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 a49347e4..72c746d2 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 @@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import net.hostsharing.hsadminng.hs.validation.PropertiesProvider; +import jakarta.persistence.EntityManager; import java.util.regex.Pattern; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; @@ -23,7 +24,7 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator { AlarmContact.isOptional(), booleanProperty("locked").readOnly(), - integerProperty("userid").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeUserId), + integerProperty("userid").computedBy(HsUnixUserHostingAssetValidator::computeUserId), integerProperty("SSD hard quota").unit("MB").maxFrom("SSD").withFactor(1024).optional(), integerProperty("SSD soft quota").unit("MB").maxFrom("SSD hard quota").optional(), @@ -44,14 +45,14 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator { return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9\\.-]+$"); } - private static String computeHomedir(final PropertiesProvider propertiesProvider) { + private static String computeHomedir(final EntityManager em, final PropertiesProvider propertiesProvider) { final var entity = (HsHostingAssetEntity) propertiesProvider; final var webspaceName = entity.getParentAsset().getIdentifier(); return "/home/pacs/" + webspaceName + "/users/" + entity.getIdentifier().substring(webspaceName.length()+DASH_LENGTH); } - private static Integer computeUserId(PropertiesProvider propertiesProvider) { - return 0; // FIXME: from a specific numeric rage and unique per hive? + private static Integer computeUserId(final EntityManager em, final PropertiesProvider propertiesProvider) { + return Math.toIntExact((Long) em.createNativeQuery("SELECT nextval('hs_hosting_asset_unixuser_system_id_seq')").getSingleResult()); } } 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 f6d72789..741b9d59 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -109,7 +109,7 @@ public abstract class HsEntityValidator { public void prepareProperties(final EntityManager em, final E entity) { stream(propertyValidators).forEach(p -> { - if ( p.isWriteOnly() && p.isComputed()) { + if (!p.isReadOnly() && p.isComputed()) { entity.directProps().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 732151cd..ac88e7a6 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java @@ -34,7 +34,7 @@ public class PasswordProperty extends StringProperty { public PasswordProperty hashedUsing(final Algorithm algorithm) { this.hashedUsing = algorithm; - computedBy((entity) + computedBy((em, entity) -> ofNullable(entity.getDirectValue(propertyName, String.class)) .map(password -> HashGenerator.using(algorithm).withRandomSalt().hash(password)) .orElse(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 d5678e12..429f97d5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import static java.lang.Boolean.FALSE; @@ -48,7 +49,7 @@ public abstract class ValidatableProperty

, T private T defaultValue; @JsonIgnore - private Function computedBy; + 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 @@ -96,7 +97,6 @@ protected void setDeferredInit(final Function[], T[]> public P readOnly() { this.readOnly = true; - optional(); return self(); } @@ -236,8 +236,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) { - throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)" ); + 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(...)" ); } } @@ -302,14 +302,14 @@ protected void setDeferredInit(final Function[], T[]> .toList(); } - public P computedBy(final Function compute) { + public P computedBy(final BiFunction compute) { this.computedBy = compute; this.computed = true; return self(); } public T compute(final EntityManager em, final E entity) { - return computedBy.apply(entity); + return computedBy.apply(em, entity); } @Override diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql index 3b1b54d1..48a79a71 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql @@ -111,6 +111,21 @@ create trigger hs_hosting_asset_type_hierarchy_check_tg --// + +-- ============================================================================ +--changeset hosting-asset-system-sequences:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +CREATE SEQUENCE IF NOT EXISTS hs_hosting_asset_unixuser_system_id_seq + AS integer + MINVALUE 100000000 + MAXVALUE 199999999 + NO CYCLE + OWNED BY NONE; + +--// + + -- ============================================================================ --changeset hosting-asset-BOOKING-ITEM-HIERARCHY-CHECK:1 endDelimiter:--// -- ---------------------------------------------------------------------------- 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 b9cfac9f..8f2614c9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java @@ -388,6 +388,35 @@ public class ImportHostingAssets extends ImportOfficeData { persistHostingAssetsOfType(EMAIL_ALIAS); } + @Test + @Order(19010) + void verifyPersistedUnixUsersWithUserId() { + assumeThatWeAreImportingControlledTestData(); + + // 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, "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}) + } + """); + } + + + // ============================================================================================ + @Test @Order(99999) void logErrors() {