From cf6bcc0b94bd70635fb2790c24d4219b6cd1533c Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 24 Jun 2024 15:29:36 +0200 Subject: [PATCH] add UnixUser HostingAsset property validation config --- .../HsHostingAssetEntityValidator.java | 4 +- .../HsUnixUserHostingAssetValidator.java | 24 ++++-- .../hs/validation/EnumerationProperty.java | 2 +- .../hs/validation/HsEntityValidator.java | 5 ++ .../hs/validation/IntegerProperty.java | 12 +++ .../hs/validation/StringProperty.java | 76 +++++++++++++++++++ .../hs/booking/item/TestHsBookingItem.java | 36 ++++++--- ...UnixUserHostingAssetValidatorUnitTest.java | 65 +++++++++++++++- 8 files changed, 204 insertions(+), 20 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java index 15ea12df..8afbf99a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidator.java @@ -47,7 +47,7 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator validate(final HsHostingAssetEntity assetEntity) { return sequentiallyValidate( - () -> validateEntityReferences(assetEntity), + () -> validateEntityReferencesAndProperties(assetEntity), () -> validateIdentifierPattern(assetEntity), // might need proper parentAsset or billingItem () -> optionallyValidate(assetEntity.getBookingItem()), () -> optionallyValidate(assetEntity.getParentAsset()), @@ -55,7 +55,7 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator validateEntityReferences(final HsHostingAssetEntity assetEntity) { + private List validateEntityReferencesAndProperties(final HsHostingAssetEntity assetEntity) { return Stream.of( validateReferencedEntity(assetEntity, "bookingItem", bookingItemValidation::validate), validateReferencedEntity(assetEntity, "parentAsset", parentAssetValidation::validate), 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 dfe222fc..75076cc7 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,14 +5,28 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import java.util.regex.Pattern; +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.StringProperty.stringProperty; + class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator { HsUnixUserHostingAssetValidator() { - super(BookingItem.mustBeNull(), - ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE), - AssignedToAsset.mustBeNull(), - AlarmContact.isOptional(), // TODO.spec: for quota notifications - NO_EXTRA_PROPERTIES); // TODO.spec: yet to be specified + super( BookingItem.mustBeNull(), + ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE), + AssignedToAsset.mustBeNull(), + AlarmContact.isOptional(), + + integerProperty("SSD hard quota").unit("GB").maxFrom("SSD").optional(), + integerProperty("SSD soft quota").unit("GB").minFrom("SSD hard quota").optional(), + integerProperty("HDD hard quota").unit("GB").maxFrom("HDD").optional(), + integerProperty("HDD soft quota").unit("GB").minFrom("HDD hard quota").optional(), + enumerationProperty("shell") + .values("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd") + .withDefault("/bin/false"), + stringProperty("homedir").readOnly(), + stringProperty("totpKey").matchesRegEx("^0x\\([0-9A-Fa-f][0-9A-Fa-f]\\)+$").minLength(12).maxLength(32).writeOnly().optional(), + stringProperty("password").minLength(8).maxLength(40).writeOnly()); // FIXME: spec } @Override diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationProperty.java index 923d7ae1..262fc1ac 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/EnumerationProperty.java @@ -35,7 +35,7 @@ public class EnumerationProperty extends ValidatableProperty { public void deferredInit(final ValidatableProperty[] allProperties) { if (deferredInit != null) { if (this.values != null) { - throw new IllegalStateException("property " + toString() + " already has values"); + throw new IllegalStateException("property " + this + " already has values"); } this.values = deferredInit.apply(allProperties); } 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 4c20f2a5..7f472583 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -40,14 +40,19 @@ public abstract class HsEntityValidator { protected ArrayList validateProperties(final Map properties) { final var result = new ArrayList(); + + // verify that all actually given properties are specified properties.keySet().forEach( givenPropName -> { if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) { result.add(givenPropName + "' is not expected but is set to '" + properties.get(givenPropName) + "'"); } }); + + // run all property validators stream(propertyValidators).forEach(pv -> { result.addAll(pv.validate(properties)); }); + return result; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerProperty.java index a1658ff9..75d3e79e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/IntegerProperty.java @@ -16,7 +16,9 @@ public class IntegerProperty extends ValidatableProperty { private String unit; private Integer min; + private String minFrom; private Integer max; + private String maxFrom; private Integer step; public static IntegerProperty integerProperty(final String propertyName) { @@ -27,6 +29,16 @@ public class IntegerProperty extends ValidatableProperty { super(Integer.class, propertyName, KEY_ORDER); } + public IntegerProperty minFrom(final String propertyName) { + minFrom = propertyName; + return this; + } + + public IntegerProperty maxFrom(final String propertyName) { + maxFrom = propertyName; + return this; + } + @Override public String unit() { return unit; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java new file mode 100644 index 00000000..2755eabb --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java @@ -0,0 +1,76 @@ +package net.hostsharing.hsadminng.hs.validation; + +import lombok.Setter; +import net.hostsharing.hsadminng.mapper.Array; + +import java.util.ArrayList; +import java.util.Map; +import java.util.regex.Pattern; + + +@Setter +public class StringProperty extends ValidatableProperty { + + private static final String[] KEY_ORDER = Array.join( + ValidatableProperty.KEY_ORDER_HEAD, + Array.of("values"), + ValidatableProperty.KEY_ORDER_TAIL); + private Pattern regExPattern; + private Integer minLength; + private Integer maxLength; + private boolean writeOnly; + private boolean readOnly; + + private StringProperty(final String propertyName) { + super(String.class, propertyName, KEY_ORDER); + } + + public static StringProperty stringProperty(final String propertyName) { + return new StringProperty(propertyName); + } + + public StringProperty minLength(final int minLength) { + this.minLength = minLength; + return this; + } + + public StringProperty maxLength(final int maxLength) { + this.maxLength = maxLength; + return this; + } + + public StringProperty matchesRegEx(final String regExPattern) { + this.regExPattern = Pattern.compile(regExPattern); + return this; + } + + public StringProperty writeOnly() { + this.writeOnly = true; + super.optional(); + return this; + } + + public StringProperty readOnly() { + this.readOnly = true; + super.optional(); + return this; + } + + @Override + protected void validate(final ArrayList result, final String propValue, final Map props) { + if (minLength != null && propValue.length()maxLength) { + result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of '" + propValue+ "' is " + propValue.length()); + } + if (regExPattern != null && !regExPattern.matcher(propValue).matches()) { + result.add(propertyName + "' is expected to be match " + regExPattern + " but '" + propValue+ "' does not match"); + } + } + + @Override + protected String simpleTypeName() { + return "string"; + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java index 0779fa2f..039e6fb1 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java @@ -12,21 +12,35 @@ import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject. @UtilityClass public class TestHsBookingItem { - public static final HsBookingItemEntity TEST_MANAGED_SERVER_BOOKING_ITEM = HsBookingItemEntity.builder() - .project(TEST_PROJECT) - .type(HsBookingItemType.MANAGED_SERVER) - .caption("test project booking item") - .resources(Map.ofEntries( - entry("someThing", 1), - entry("anotherThing", "blue") - )) - .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) - .build(); - public static final HsBookingItemEntity TEST_CLOUD_SERVER_BOOKING_ITEM = HsBookingItemEntity.builder() .project(TEST_PROJECT) .type(HsBookingItemType.CLOUD_SERVER) .caption("test cloud server booking item") .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) .build(); + + public static final HsBookingItemEntity TEST_MANAGED_SERVER_BOOKING_ITEM = HsBookingItemEntity.builder() + .project(TEST_PROJECT) + .type(HsBookingItemType.MANAGED_SERVER) + .caption("test project booking item") + .resources(Map.ofEntries( + entry("CPUs", 2), + entry("RAM", 4), + entry("SSD", 50), + entry("Traffic", 250) + )) + .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) + .build(); + + public static final HsBookingItemEntity TEST_MANAGED_WEBSPACE_BOOKING_ITEM = HsBookingItemEntity.builder() + .parentItem(TEST_MANAGED_SERVER_BOOKING_ITEM) + .type(HsBookingItemType.MANAGED_WEBSPACE) + .caption("test managed webspace item") + .resources(Map.ofEntries( + entry("SSD", 25), + entry("Traffic", 250) + )) + .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) + .build(); + } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java index afe265b0..3e9b8271 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidatorUnitTest.java @@ -1,14 +1,78 @@ 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 org.junit.jupiter.api.Test; +import java.util.Map; + +import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM; +import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_WEBSPACE_BOOKING_ITEM; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER; +import static net.hostsharing.hsadminng.mapper.PatchMap.entry; import static org.assertj.core.api.Assertions.assertThat; class HsUnixUserHostingAssetValidatorUnitTest { + private final HsHostingAssetEntity TEST_MANAGED_SERVER_HOSTING_ASSET = HsHostingAssetEntity.builder() + .type(HsHostingAssetType.MANAGED_SERVER) + .identifier("vm1234") + .caption("some managed server") + .bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM) + .build(); + private HsHostingAssetEntity TEST_MANAGED_WEBSPACE_HOSTING_ASSET = HsHostingAssetEntity.builder() + .type(MANAGED_WEBSPACE) + .bookingItem(TEST_MANAGED_WEBSPACE_BOOKING_ITEM) + .parentAsset(TEST_MANAGED_SERVER_HOSTING_ASSET) + .identifier("abc00") + .build();; + + @Test + void validatesValidUnixUser() { + // given + final var unixUserHostingAsset = HsHostingAssetEntity.builder() + .type(UNIX_USER) + .parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET) + .identifier("abc00-temp") + .caption("some valid test UnixUser") + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType()); + + // when + final var result = validator.validate(unixUserHostingAsset); + + // then + assertThat(result).isEmpty(); + } + + @Test + void validatesUnixUserProperties() { + // given + final var unixUserHostingAsset = HsHostingAssetEntity.builder() + .type(UNIX_USER) + .parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET) + .identifier("abc00-temp") + .caption("some test UnixUser with invalid properties") + .config(Map.ofEntries( + entry("SSD hard quota", 1000), + entry("SSD soft quota", 2000), + entry("HDD hard quota", 1000), + entry("HDD soft quota", 2000), + entry("homedir", "/is/read-only"), + entry("totpKey", "should be a hex number"), + entry("password", "should be a hex number") + )) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType()); + + // when + final var result = validator.validate(unixUserHostingAsset); + + // then + assertThat(result).isEmpty(); + } + @Test void validatesInvalidIdentifier() { // given @@ -19,7 +83,6 @@ class HsUnixUserHostingAssetValidatorUnitTest { .build(); final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType()); - // when final var result = validator.validate(unixUserHostingAsset);