diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java index 5a0eb885..a9a9c879 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java @@ -32,6 +32,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; +import jakarta.persistence.PostLoad; import jakarta.persistence.Table; import jakarta.persistence.Transient; import jakarta.persistence.Version; @@ -124,6 +125,14 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject resourcesWrapper; + @Transient + private boolean isLoaded; + + @PostLoad + public void markAsLoaded() { + this.isLoaded = true; + } + public PatchableMapWrapper getResources() { return PatchableMapWrapper.of(resourcesWrapper, (newWrapper) -> {resourcesWrapper = newWrapper; }, resources ); } 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 495ea665..9495340e 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 @@ -40,6 +40,17 @@ public class HostingAssetEntitySaveProcessor { return this; } + /// validates the entity itself including its properties, but ignoring some error messages for import of legacy data + public HostingAssetEntitySaveProcessor validateEntityIgnoring(final String ignoreRegExp) { + step("validateEntity", "prepareForSave"); + MultiValidationException.throwIfNotEmpty( + validator.validateEntity(entity).stream() + .filter(errorMsg -> !errorMsg.matches(ignoreRegExp)) + .toList() + ); + return this; + } + /// hashing passwords etc. @SuppressWarnings("unchecked") public HostingAssetEntitySaveProcessor prepareForSave() { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java index b24efe3b..97c44ce2 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java @@ -18,7 +18,7 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator { // according to RFC 1035 (section 5) and RFC 1034 - static final String RR_REGEX_NAME = "([a-z0-9\\._-]+|@)\\s+"; + static final String RR_REGEX_NAME = "([a-z0-9\\.-]+|@)\\s+"; static final String RR_REGEX_TTL = "(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*"; static final String RR_REGEX_IN = "IN\\s+"; // record class IN for Internet static final String RR_RECORD_TYPE = "[A-Z]+\\s+"; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java index 0d913879..965c2d1b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java @@ -9,7 +9,6 @@ import jakarta.persistence.EntityManager; import java.util.regex.Pattern; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; -import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordProperty; import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty; @@ -24,16 +23,17 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator { AlarmContact.isOptional(), booleanProperty("locked").readOnly(), - integerProperty("userid").computedBy(HsUnixUserHostingAssetValidator::computeUserId), + integerProperty("userid").readOnly().initializedBy(HsUnixUserHostingAssetValidator::computeUserId), integerProperty("SSD hard quota").unit("MB").maxFrom("SSD").withFactor(1024).optional(), integerProperty("SSD soft quota").unit("MB").maxFrom("SSD hard quota").optional(), integerProperty("HDD hard quota").unit("MB").maxFrom("HDD").withFactor(1024).optional(), integerProperty("HDD soft quota").unit("MB").maxFrom("HDD hard quota").optional(), stringProperty("shell") + // TODO.spec: do we want to change them all to /usr/bin/, also in import? .provided("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd") .withDefault("/bin/false"), - stringProperty("homedir").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeHomedir), + stringProperty("homedir").readOnly().renderedBy(HsUnixUserHostingAssetValidator::computeHomedir), stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).undisclosed().writeOnly().optional(), passwordProperty("password").minLength(8).maxLength(40).hashedUsing(HashGenerator.Algorithm.LINUX_SHA512).writeOnly()); // TODO.spec: public SSH keys? (only if hsadmin-ng is only accessible with 2FA) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java index 741b9d59..77cc2514 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -14,6 +14,9 @@ import java.util.stream.Collectors; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; +import static net.hostsharing.hsadminng.hs.validation.ValidatableProperty.ComputeMode.IN_INIT; +import static net.hostsharing.hsadminng.hs.validation.ValidatableProperty.ComputeMode.IN_PREP; +import static net.hostsharing.hsadminng.hs.validation.ValidatableProperty.ComputeMode.IN_REVAMP; // TODO.refa: rename to HsEntityProcessor, also subclasses public abstract class HsEntityValidator { @@ -109,7 +112,7 @@ public abstract class HsEntityValidator { public void prepareProperties(final EntityManager em, final E entity) { stream(propertyValidators).forEach(p -> { - if (!p.isReadOnly() && p.isComputed()) { + if (p.isComputed(IN_PREP) || p.isComputed(IN_INIT) && !entity.isLoaded() ) { entity.directProps().put(p.propertyName, p.compute(em, entity)); } }); @@ -120,7 +123,7 @@ public abstract class HsEntityValidator { stream(propertyValidators).forEach(p -> { if (p.isWriteOnly()) { copy.remove(p.propertyName); - } else if (p.isReadOnly() && p.isComputed()) { + } else if (p.isComputed(IN_REVAMP)) { copy.put(p.propertyName, p.compute(em, entity)); } }); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java index ac88e7a6..083e69ca 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java @@ -1,8 +1,8 @@ package net.hostsharing.hsadminng.hs.validation; +import lombok.Setter; import net.hostsharing.hsadminng.hash.HashGenerator; import net.hostsharing.hsadminng.hash.HashGenerator.Algorithm; -import lombok.Setter; import java.util.List; import java.util.stream.Stream; @@ -13,7 +13,10 @@ import static net.hostsharing.hsadminng.mapper.Array.insertNewEntriesAfterExisti @Setter public class PasswordProperty extends StringProperty { - private static final String[] KEY_ORDER = insertNewEntriesAfterExistingEntry(StringProperty.KEY_ORDER, "computed", "hashedUsing"); + private static final String[] KEY_ORDER = insertNewEntriesAfterExistingEntry( + StringProperty.KEY_ORDER, + "computed", + "hashedUsing"); private Algorithm hashedUsing; @@ -34,10 +37,11 @@ public class PasswordProperty extends StringProperty { public PasswordProperty hashedUsing(final Algorithm algorithm) { this.hashedUsing = algorithm; - computedBy((em, entity) - -> ofNullable(entity.getDirectValue(propertyName, String.class)) - .map(password -> HashGenerator.using(algorithm).withRandomSalt().hash(password)) - .orElse(null)); + computedBy( + ComputeMode.IN_PREP, + (em, entity) -> ofNullable(entity.getDirectValue(propertyName, String.class)) + .map(password -> HashGenerator.using(algorithm).withRandomSalt().hash(password)) + .orElse(null)); return self(); } @@ -69,9 +73,10 @@ public class PasswordProperty extends StringProperty { } } - final long groupsCovered = Stream.of(hasLowerCase, hasUpperCase, hasDigit, hasSpecialChar).filter(v->v).count(); - if ( groupsCovered < 3) { - result.add(propertyName + "' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters"); + final long groupsCovered = Stream.of(hasLowerCase, hasUpperCase, hasDigit, hasSpecialChar).filter(v -> v).count(); + if (groupsCovered < 3) { + result.add(propertyName + + "' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters"); } if (containsColon) { result.add(propertyName + "' must not contain colon (':')"); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/PropertiesProvider.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/PropertiesProvider.java index c4d60fb8..363e0126 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/PropertiesProvider.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/PropertiesProvider.java @@ -4,6 +4,7 @@ import java.util.Map; public interface PropertiesProvider { + boolean isLoaded(); Map directProps(); Object getContextValue(final String propName); @@ -11,6 +12,10 @@ public interface PropertiesProvider { return cast(propName, directProps().get(propName), clazz, null); } + default T getDirectValue(final String propName, final Class clazz, final T defaultValue) { + return cast(propName, directProps().get(propName), clazz, defaultValue); + } + default T getContextValue(final String propName, final Class clazz) { return cast(propName, getContextValue(propName), clazz, null); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java index 429f97d5..0d8fa604 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java @@ -48,11 +48,17 @@ public abstract class ValidatableProperty

, T private Set requiresAtMaxOneOf; private T defaultValue; + protected enum ComputeMode { + IN_INIT, + IN_PREP, + IN_REVAMP + } + @JsonIgnore private BiFunction computedBy; @Accessors(makeFinal = true, chain = true, fluent = false) - private boolean computed; // used in descriptor, because computedBy cannot be rendered to a text string + private ComputeMode computed; // name 'computed' instead 'computeMode' for better readability in property description @Accessors(makeFinal = true, chain = true, fluent = false) private boolean readOnly; @@ -77,7 +83,7 @@ public abstract class ValidatableProperty

, T return null; } -protected void setDeferredInit(final Function[], T[]> function) { + protected void setDeferredInit(final Function[], T[]> function) { this.deferredInit = function; } @@ -236,8 +242,8 @@ protected void setDeferredInit(final Function[], T[]> protected abstract void validate(final List result, final T propValue, final PropertiesProvider propProvider); public void verifyConsistency(final Map.Entry, ?> typeDef) { - if (required == null && requiresAtLeastOneOf == null && requiresAtMaxOneOf == null && !readOnly && !computed) { - throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .computed(...), .readOnly(), .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)" ); + if (required == null && requiresAtLeastOneOf == null && requiresAtMaxOneOf == null && !readOnly && defaultValue == null) { + throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .readOnly(), .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)" ); } } @@ -302,12 +308,24 @@ protected void setDeferredInit(final Function[], T[]> .toList(); } - public P computedBy(final BiFunction compute) { + public P initializedBy(final BiFunction compute) { + return computedBy(ComputeMode.IN_INIT, compute); + } + + public P renderedBy(final BiFunction compute) { + return computedBy(ComputeMode.IN_REVAMP, compute); + } + + protected P computedBy(final ComputeMode computeMode, final BiFunction compute) { this.computedBy = compute; - this.computed = true; + this.computed = computeMode; return self(); } + public boolean isComputed(final ComputeMode computeMode) { + return computed == computeMode; + } + public T compute(final EntityManager em, final E entity) { return computedBy.apply(em, entity); } 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 d6ee39d0..b124c08a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java @@ -26,6 +26,8 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; @@ -230,7 +232,7 @@ public class CsvDataImport extends ContextBasedTest { } void logErrors() { - assumeThat(errors).isEmpty(); + assertThat(errors).isEmpty(); } } @@ -298,12 +300,17 @@ class Record { } } +@Retention(RetentionPolicy.RUNTIME) +@interface ContinueOnFailure { +} + class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback { private static boolean previousTestsPassed = true; - public void testFailed(ExtensionContext context, Throwable cause) { - previousTestsPassed = false; + @Override + public void testFailed(final ExtensionContext context, final Throwable cause) { + previousTestsPassed = previousTestsPassed && context.getElement().map(e -> e.isAnnotationPresent(ContinueOnFailure.class)).orElse(false); } @Override 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 beeb3391..ddcf27da 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java @@ -287,20 +287,20 @@ public class ImportHostingAssets extends ImportOfficeData { // no contacts yet => mostly null values assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace(""" { - 4005803=HsHostingAssetEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102090}), - 4005805=HsHostingAssetEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102091}), - 4005809=HsHostingAssetEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102093}), - 4005811=HsHostingAssetEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102094}), - 4005813=HsHostingAssetEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102095}), - 4005835=HsHostingAssetEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102106}), - 4005964=HsHostingAssetEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102147}), - 4005966=HsHostingAssetEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "shell": "/bin/bash", "userid": 102148}), - 4005990=HsHostingAssetEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102160}), + 4005803=HsHostingAssetEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102090}), + 4005805=HsHostingAssetEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102091}), + 4005809=HsHostingAssetEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 8, "SSD soft quota": 4, "locked": false, "shell": "/bin/bash", "userid": 102093}), + 4005811=HsHostingAssetEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102094}), + 4005813=HsHostingAssetEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102095}), + 4005835=HsHostingAssetEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 1024, "SSD soft quota": 1024, "locked": false, "shell": "/usr/bin/passwd", "userid": 102106}), + 4005964=HsHostingAssetEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102147}), + 4005966=HsHostingAssetEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "shell": "/bin/bash", "userid": 102148}), + 4005990=HsHostingAssetEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102160}), 4100705=HsHostingAssetEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 10003}), 4100824=HsHostingAssetEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 10000}), 4167846=HsHostingAssetEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 110568}), - 4169546=HsHostingAssetEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110593}), - 4169596=HsHostingAssetEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110594}) + 4169546=HsHostingAssetEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110593}), + 4169596=HsHostingAssetEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110594}) } """); } @@ -324,12 +324,16 @@ public class ImportHostingAssets extends ImportOfficeData { // no contacts yet => mostly null values assertThat(firstOfEachType(15, EMAIL_ALIAS)).isEqualToIgnoringWhitespace(""" { - 4002403=HsHostingAssetEntity(EMAIL_ALIAS, lug00, lug00, MANAGED_WEBSPACE:lug00, { "target": "[michael.mellis@example.com]"}), - 4002405=HsHostingAssetEntity(EMAIL_ALIAS, lug00-wla-listar, lug00-wla-listar, MANAGED_WEBSPACE:lug00, { "target": "[|/home/pacs/lug00/users/in/mailinglist/listar]"}), - 4002429=HsHostingAssetEntity(EMAIL_ALIAS, mim00, mim00, MANAGED_WEBSPACE:mim00, { "target": "[mim12-mi@mim12.hostsharing.net]"}), - 4002431=HsHostingAssetEntity(EMAIL_ALIAS, mim00-abruf, mim00-abruf, MANAGED_WEBSPACE:mim00, { "target": "[michael.mellis@hostsharing.net]"}), - 4002449=HsHostingAssetEntity(EMAIL_ALIAS, mim00-hhfx, mim00-hhfx, MANAGED_WEBSPACE:mim00, { "target": "[mim00-hhfx, |/usr/bin/formail -I 'Reply-To: hamburger-fx@example.net' | /usr/lib/sendmail mim00-hhfx-l]"}), - 4002451=HsHostingAssetEntity(EMAIL_ALIAS, mim00-hhfx-l, mim00-hhfx-l, MANAGED_WEBSPACE:mim00, { "target": "[:include:/home/pacs/mim00/etc/hhfx.list]"}) + 5002403=HsHostingAssetEntity(EMAIL_ALIAS, lug00, lug00, MANAGED_WEBSPACE:lug00, { "target": "[michael.mellis@example.com]"}), + 5002405=HsHostingAssetEntity(EMAIL_ALIAS, lug00-wla-listar, lug00-wla-listar, MANAGED_WEBSPACE:lug00, { "target": "[|/home/pacs/lug00/users/in/mailinglist/listar]"}), + 5002429=HsHostingAssetEntity(EMAIL_ALIAS, mim00, mim00, MANAGED_WEBSPACE:mim00, { "target": "[mim12-mi@mim12.hostsharing.net]"}), + 5002431=HsHostingAssetEntity(EMAIL_ALIAS, mim00-abruf, mim00-abruf, MANAGED_WEBSPACE:mim00, { "target": "[michael.mellis@hostsharing.net]"}), + 5002449=HsHostingAssetEntity(EMAIL_ALIAS, mim00-hhfx, mim00-hhfx, MANAGED_WEBSPACE:mim00, { "target": "[mim00-hhfx, |/usr/bin/formail -I 'Reply-To: hamburger-fx@example.net' | /usr/lib/sendmail mim00-hhfx-l]"}), + 5002451=HsHostingAssetEntity(EMAIL_ALIAS, mim00-hhfx-l, mim00-hhfx-l, MANAGED_WEBSPACE:mim00, { "target": "[:include:/home/pacs/mim00/etc/hhfx.list]"}), + 5002452=HsHostingAssetEntity(EMAIL_ALIAS, mim00-empty, mim00-empty, MANAGED_WEBSPACE:mim00, { "target": "[]"}), + 5002453=HsHostingAssetEntity(EMAIL_ALIAS, mim00-0_entries, mim00-0_entries, MANAGED_WEBSPACE:mim00, { "target": "[]"}), + 5002454=HsHostingAssetEntity(EMAIL_ALIAS, mim00-dev.null, mim00-dev.null, MANAGED_WEBSPACE:mim00, { "target": "[/dev/null]"}), + 5002455=HsHostingAssetEntity(EMAIL_ALIAS, mim00-1_with_space, mim00-1_with_space, MANAGED_WEBSPACE:mim00, { "target": "[|/home/pacs/mim00/install/corpslistar/listar]"}) } """); } @@ -337,7 +341,7 @@ public class ImportHostingAssets extends ImportOfficeData { // -------------------------------------------------------------------------------------------- @Test - @Order(11400) + @Order(18010) void validateBookingItems() { bookingItems.forEach((id, bi) -> { try { @@ -349,19 +353,27 @@ public class ImportHostingAssets extends ImportOfficeData { } @Test - @Order(11410) + @Order(18020) void validateHostingAssets() { hostingAssets.forEach((id, ha) -> { try { new HostingAssetEntitySaveProcessor(em, ha) .preprocessEntity() - .validateEntity(); + .validateEntityIgnoring("'EMAIL_ALIAS:.*\\.config\\.target' .*") + .prepareForSave(); } catch (final Exception exc) { errors.add("validation failed for id:" + id + "( " + ha + "): " + exc.getMessage()); } }); } + @Test + @Order(18999) + @ContinueOnFailure + void logValidationErrors() { + super.logErrors(); + } + // -------------------------------------------------------------------------------------------- @Test @@ -389,30 +401,28 @@ public class ImportHostingAssets extends ImportOfficeData { persistHostingAssetsOfType(EMAIL_ALIAS); } - @Test @Order(19010) void verifyPersistedUnixUsersWithUserId() { assumeThatWeAreImportingControlledTestData(); // no contacts yet => mostly null value - // FIXME: keep original userids assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace(""" { - 4005803=HsHostingAssetEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000000}), - 4005805=HsHostingAssetEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000001}), - 4005809=HsHostingAssetEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000002}), - 4005811=HsHostingAssetEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 100000003}), - 4005813=HsHostingAssetEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 100000004}), - 4005835=HsHostingAssetEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 100000005}), - 4005964=HsHostingAssetEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000006}), - 4005966=HsHostingAssetEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000007}), - 4005990=HsHostingAssetEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000008}), - 4100705=HsHostingAssetEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/false", "userid": 100000009}), - 4100824=HsHostingAssetEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000010}), - 4167846=HsHostingAssetEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/false", "userid": 100000011}), - 4169546=HsHostingAssetEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000012}), - 4169596=HsHostingAssetEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000013}) + 4005803=HsHostingAssetEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102090}), + 4005805=HsHostingAssetEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102091}), + 4005809=HsHostingAssetEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 8, "SSD soft quota": 4, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102093}), + 4005811=HsHostingAssetEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102094}), + 4005813=HsHostingAssetEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102095}), + 4005835=HsHostingAssetEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 1024, "SSD soft quota": 1024, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102106}), + 4005964=HsHostingAssetEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102147}), + 4005966=HsHostingAssetEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102148}), + 4005990=HsHostingAssetEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102160}), + 4100705=HsHostingAssetEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/false", "userid": 10003}), + 4100824=HsHostingAssetEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 10000}), + 4167846=HsHostingAssetEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/false", "userid": 110568}), + 4169546=HsHostingAssetEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 110593}), + 4169596=HsHostingAssetEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 110594}) } """); } @@ -436,12 +446,12 @@ public class ImportHostingAssets extends ImportOfficeData { private void persistHostingAssetsOfType(final HsHostingAssetType hsHostingAssetType) { jpaAttempt.transacted(() -> { - context(rbacSuperuser); hostingAssets.forEach((key, ha) -> { + context(rbacSuperuser); if (ha.getType() == hsHostingAssetType) { new HostingAssetEntitySaveProcessor(em, ha) .preprocessEntity() - .validateEntity() + .validateEntityIgnoring("'EMAIL_ALIAS:.*\\.config\\.target' .*") .prepareForSave() .saveUsing(entity -> persist(key, entity)) .validateContext(); @@ -522,8 +532,8 @@ public class ImportHostingAssets extends ImportOfficeData { .isTrue()); final var asset = HsHostingAssetEntity.builder() - .isLoaded(haType - == MANAGED_WEBSPACE) // this turns off identifier validation to accept former default prefixes + // this turns off identifier validation to accept former default prefixes + .isLoaded(haType == MANAGED_WEBSPACE) .type(haType) .identifier(packet_name) .bookingItem(bookingItem) @@ -663,9 +673,10 @@ public class ImportHostingAssets extends ImportOfficeData { final var packet_id = rec.getInteger("packet_id"); final var unixUserAsset = HsHostingAssetEntity.builder() .type(UNIX_USER) - .parentAsset(hostingAssets.get(PACKET_ID_OFFSET+packet_id)) + .parentAsset(hostingAssets.get(PACKET_ID_OFFSET + packet_id)) .identifier(rec.getString("name")) .caption(rec.getString("comment")) + .isLoaded(true) // avoid overwriting imported userids with generated ids .config(new HashMap<>(Map.ofEntries( entry("shell", rec.getString("shell")), // entry("homedir", rec.getString("homedir")), do not import, it's calculated @@ -677,6 +688,33 @@ public class ImportHostingAssets extends ImportOfficeData { entry("HDD hard quota", rec.getInteger("storage_hardlimit")) ))) .build(); + + // TODO.spec: crop SSD+HDD limits if > booked + if (unixUserAsset.getDirectValue("SSD hard quota", Integer.class, 0) + > 1024*unixUserAsset.getContextValue("SSD", Integer.class, 0)) { + unixUserAsset.getConfig().put("SSD hard quota", unixUserAsset.getContextValue("SSD", Integer.class, 0)*1024); + } + if (unixUserAsset.getDirectValue("HDD hard quota", Integer.class, 0) + > 1024*unixUserAsset.getContextValue("HDD", Integer.class, 0)) { + unixUserAsset.getConfig().put("HDD hard quota", unixUserAsset.getContextValue("HDD", Integer.class, 0)*1024); + } + + // TODO.spec: does `softlimit unixUserAsset.getDirectValue("SSD hard quota", Integer.class, 0)) { + unixUserAsset.getConfig().put("SSD soft quota", unixUserAsset.getConfig().get("SSD hard quota")); + } + if (unixUserAsset.getDirectValue("HDD soft quota", Integer.class, 0) + > unixUserAsset.getDirectValue("HDD hard quota", Integer.class, 0)) { + unixUserAsset.getConfig().put("HDD soft quota", unixUserAsset.getConfig().get("HDD hard quota")); + } + + // TODO.spec: remove HDD limits if no HDD storage is booked + if (unixUserAsset.getContextValue("HDD", Integer.class, 0) == 0) { + unixUserAsset.getConfig().remove("HDD hard quota"); + unixUserAsset.getConfig().remove("HDD soft quota"); + } + hostingAssets.put(UNIXUSER_ID_OFFSET + unixuser_id, unixUserAsset); }); } @@ -692,7 +730,7 @@ public class ImportHostingAssets extends ImportOfficeData { final var targets = parseCsvLine(rec.getString("target")); final var unixUserAsset = HsHostingAssetEntity.builder() .type(EMAIL_ALIAS) - .parentAsset(hostingAssets.get(PACKET_ID_OFFSET+packet_id)) + .parentAsset(hostingAssets.get(PACKET_ID_OFFSET + packet_id)) .identifier(rec.getString("name")) .caption(rec.getString("name")) .config(Map.ofEntries( diff --git a/src/test/java/net/hostsharing/hsadminng/hs/validation/PasswordPropertyUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/validation/PasswordPropertyUnitTest.java index 47e40336..aea913e5 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/validation/PasswordPropertyUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/validation/PasswordPropertyUnitTest.java @@ -103,6 +103,11 @@ class PasswordPropertyUnitTest { // when final var result = passwordProp.compute(em, new PropertiesProvider() { + @Override + public boolean isLoaded() { + return false; + } + @Override public Map directProps() { return Map.ofEntries( diff --git a/src/test/resources/migration/hosting/emailalias.csv b/src/test/resources/migration/hosting/emailalias.csv index 7701c61a..7d5cd887 100644 --- a/src/test/resources/migration/hosting/emailalias.csv +++ b/src/test/resources/migration/hosting/emailalias.csv @@ -5,3 +5,7 @@ emailalias_id;pac_id;name;target 2431;1112;mim00-abruf;michael.mellis@hostsharing.net 2449;1112;mim00-hhfx;"mim00-hhfx,""|/usr/bin/formail -I 'Reply-To: hamburger-fx@example.net' | /usr/lib/sendmail mim00-hhfx-l""" 2451;1112;mim00-hhfx-l;:include:/home/pacs/mim00/etc/hhfx.list +2452;1112;mim00-empty; +2453;1112;mim00-0_entries;"" +2454;1112;mim00-dev.null; /dev/null +2455;1112;mim00-1_with_space;" ""|/home/pacs/mim00/install/corpslistar/listar""" diff --git a/src/test/resources/migration/hosting/unixuser.csv b/src/test/resources/migration/hosting/unixuser.csv index ee08e2f0..68538a04 100644 --- a/src/test/resources/migration/hosting/unixuser.csv +++ b/src/test/resources/migration/hosting/unixuser.csv @@ -2,11 +2,11 @@ unixuser_id;name;comment;shell;homedir;locked;packet_id;userid;quota_softlimit;q 100824;hsh00;Hostsharing Paket;/bin/bash;/home/pacs/hsh00;0;630;10000;0;0;0;0 5803;lug00;LUGs;/bin/bash;/home/pacs/lug00;0;1094;102090;0;0;0;0 -5805;lug00-wla.1;Paul Klemm;/bin/bash;/home/pacs/lug00/users/deaf;0;1094;102091;0;0;0;0 -5809;lug00-wla.2;Walter Müller;/bin/bash;/home/pacs/lug00/users/marl;0;1094;102093;0;0;0;0 +5805;lug00-wla.1;Paul Klemm;/bin/bash;/home/pacs/lug00/users/deaf;0;1094;102091;4;0;0;0 +5809;lug00-wla.2;Walter Müller;/bin/bash;/home/pacs/lug00/users/marl;0;1094;102093;4;8;0;0 5811;lug00-ola.a;LUG OLA - POP a;/usr/bin/passwd;/home/pacs/lug00/users/marl.a;1;1094;102094;0;0;0;0 5813;lug00-ola.b;LUG OLA - POP b;/usr/bin/passwd;/home/pacs/lug00/users/marl.b;1;1094;102095;0;0;0;0 -5835;lug00-test;Test;/usr/bin/passwd;/home/pacs/lug00/users/test;0;1094;102106;0;0;0;0 +5835;lug00-test;Test;/usr/bin/passwd;/home/pacs/lug00/users/test;0;1094;102106;2000000;4000000;20;0 100705;hsh00-mim;Michael Mellis;/bin/false;/home/pacs/hsh00/users/mi;0;630;10003;0;0;0;0 5964;mim00;Michael Mellis;/bin/bash;/home/pacs/mim00;0;1112;102147;0;0;0;0