add-unix-user-hosting-asset-validation #66

Merged
hsh-michaelhoennig merged 11 commits from add-unix-user-hosting-asset-validation into master 2024-06-27 12:39:45 +02:00
16 changed files with 125 additions and 38 deletions
Showing only changes of commit 330ae92c05 - Show all commits

View File

@ -11,6 +11,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -42,6 +43,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static java.util.Collections.emptyMap;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@ -68,7 +70,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class HsBookingItemEntity implements Stringifyable, RbacObject { public class HsBookingItemEntity implements Stringifyable, RbacObject, PropertiesProvider {
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class) private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
.withProp(HsBookingItemEntity::getProject) .withProp(HsBookingItemEntity::getProject)
@ -146,6 +148,23 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
return upperInclusiveFromPostgresDateRange(getValidity()); return upperInclusiveFromPostgresDateRange(getValidity());
} }
@Override
public Map<String, Object> directProps() {
return resources;
}
@Override
public Object getContextValue(final String propName) {
final var v = resources.get(propName);
if (v!= null) {
return v;
}
if (parentItem!=null) {
return parentItem.getResources().get(propName);
}
return emptyMap();
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);

View File

@ -29,7 +29,7 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
} }
private List<String> validateProperties(final HsBookingItemEntity bookingItem) { private List<String> validateProperties(final HsBookingItemEntity bookingItem) {
return enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources())); return enrich(prefix(bookingItem.toShortString(), "resources"), super.validateProperties(bookingItem));
} }
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) { private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {

View File

@ -9,6 +9,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -39,6 +40,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static java.util.Collections.emptyMap;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
@ -63,7 +65,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class HsHostingAssetEntity implements Stringifyable, RbacObject { public class HsHostingAssetEntity implements Stringifyable, RbacObject, PropertiesProvider {
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class) private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
.withProp(HsHostingAssetEntity::getType) .withProp(HsHostingAssetEntity::getType)
@ -122,7 +124,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
private PatchableMapWrapper<Object> configWrapper; private PatchableMapWrapper<Object> configWrapper;
@Transient @Transient
private boolean isLoaded = false; private boolean isLoaded;
@PostLoad @PostLoad
public void markAsLoaded() { public void markAsLoaded() {
@ -137,6 +139,28 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfig); PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfig);
} }
@Override
public Map<String, Object> directProps() {
return config;
}
@Override
public Object getContextValue(final String propName) {
final var v = config.get(propName);
if (v!= null) {
return v;
}
if (bookingItem!=null) {
return bookingItem.getResources().get(propName);
}
if (parentAsset!=null && parentAsset.getBookingItem()!=null) {
return parentAsset.getBookingItem().getResources().get(propName);
}
return emptyMap();
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);

View File

@ -76,7 +76,7 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
} }
private List<String> validateProperties(final HsHostingAssetEntity assetEntity) { private List<String> validateProperties(final HsHostingAssetEntity assetEntity) {
return enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig())); return enrich(prefix(assetEntity.toShortString(), "config"), super.validateProperties(assetEntity));
} }
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) { private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {

View File

@ -18,14 +18,14 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
AlarmContact.isOptional(), AlarmContact.isOptional(),
integerProperty("SSD hard quota").unit("GB").maxFrom("SSD").optional(), integerProperty("SSD hard quota").unit("GB").maxFrom("SSD").optional(),
integerProperty("SSD soft quota").unit("GB").minFrom("SSD hard quota").optional(), integerProperty("SSD soft quota").unit("GB").maxFrom("SSD hard quota").optional(),
integerProperty("HDD hard quota").unit("GB").maxFrom("HDD").optional(), integerProperty("HDD hard quota").unit("GB").maxFrom("HDD").optional(),
integerProperty("HDD soft quota").unit("GB").minFrom("HDD hard quota").optional(), integerProperty("HDD soft quota").unit("GB").maxFrom("HDD hard quota").optional(),
enumerationProperty("shell") enumerationProperty("shell")
.values("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd") .values("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd")
.withDefault("/bin/false"), .withDefault("/bin/false"),
stringProperty("homedir").readOnly(), stringProperty("homedir").readOnly(),
stringProperty("totpKey").matchesRegEx("^0x\\([0-9A-Fa-f][0-9A-Fa-f]\\)+$").minLength(12).maxLength(32).writeOnly().optional(), stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).writeOnly().optional(),
stringProperty("password").minLength(8).maxLength(40).writeOnly()); // FIXME: spec stringProperty("password").minLength(8).maxLength(40).writeOnly()); // FIXME: spec
} }

View File

@ -29,9 +29,9 @@ public class BooleanProperty extends ValidatableProperty<Boolean> {
} }
@Override @Override
protected void validate(final ArrayList<String> result, final Boolean propValue, final Map<String, Object> props) { protected void validate(final ArrayList<String> result, final Boolean propValue, final PropertiesProvider propProvider) {
if (falseIf != null && propValue) { if (falseIf != null && propValue) {
final Object referencedValue = props.get(falseIf.getKey()); final Object referencedValue = propProvider.directProps().get(falseIf.getKey());
if (Objects.equals(referencedValue, falseIf.getValue())) { if (Objects.equals(referencedValue, falseIf.getValue())) {
result.add(propertyName + "' is expected to be false because " + result.add(propertyName + "' is expected to be false because " +
falseIf.getKey() + "=" + referencedValue + " but is " + propValue); falseIf.getKey() + "=" + referencedValue + " but is " + propValue);

View File

@ -5,7 +5,6 @@ import net.hostsharing.hsadminng.mapper.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
@ -51,7 +50,7 @@ public class EnumerationProperty extends ValidatableProperty<String> {
} }
@Override @Override
protected void validate(final ArrayList<String> result, final String propValue, final Map<String, Object> props) { protected void validate(final ArrayList<String> result, final String propValue, final PropertiesProvider propProvider) {
if (stream(values).noneMatch(v -> v.equals(propValue))) { if (stream(values).noneMatch(v -> v.equals(propValue))) {
result.add(propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'"); result.add(propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
} }

View File

@ -38,10 +38,11 @@ public abstract class HsEntityValidator<E> {
.toList(); .toList();
} }
protected ArrayList<String> validateProperties(final Map<String, Object> properties) { protected ArrayList<String> validateProperties(final PropertiesProvider propsProvider) {
final var result = new ArrayList<String>(); final var result = new ArrayList<String>();
// verify that all actually given properties are specified // verify that all actually given properties are specified
final var properties = propsProvider.directProps();
properties.keySet().forEach( givenPropName -> { properties.keySet().forEach( givenPropName -> {
if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(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) + "'"); result.add(givenPropName + "' is not expected but is set to '" + properties.get(givenPropName) + "'");
@ -50,7 +51,7 @@ public abstract class HsEntityValidator<E> {
// run all property validators // run all property validators
stream(propertyValidators).forEach(pv -> { stream(propertyValidators).forEach(pv -> {
result.addAll(pv.validate(properties)); result.addAll(pv.validate(propsProvider));
}); });
return result; return result;

View File

@ -2,9 +2,9 @@ package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array; import net.hostsharing.hsadminng.mapper.Array;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map;
@Setter @Setter
public class IntegerProperty extends ValidatableProperty<Integer> { public class IntegerProperty extends ValidatableProperty<Integer> {
@ -29,6 +29,12 @@ public class IntegerProperty extends ValidatableProperty<Integer> {
super(Integer.class, propertyName, KEY_ORDER); super(Integer.class, propertyName, KEY_ORDER);
} }
@Override
public void deferredInit(final ValidatableProperty<?>[] allProperties) {
Validate.isTrue(min == null || minFrom == null, "min and minFrom are exclusive, but both are given");
Validate.isTrue(max == null || maxFrom == null, "max and maxFrom are exclusive, but both are given");
}
public IntegerProperty minFrom(final String propertyName) { public IntegerProperty minFrom(final String propertyName) {
minFrom = propertyName; minFrom = propertyName;
return this; return this;
@ -49,20 +55,34 @@ public class IntegerProperty extends ValidatableProperty<Integer> {
} }
@Override @Override
protected void validate(final ArrayList<String> result, final Integer propValue, final Map<String, Object> props) { protected void validate(final ArrayList<String> result, final Integer propValue, final PropertiesProvider propProvider) {
if (min != null && propValue < min) { validateMin(result, propertyName, propValue, min);
result.add(propertyName + "' is expected to be >= " + min + " but is " + propValue); validateMax(result, propertyName, propValue, max);
}
if (max != null && propValue > max) {
result.add(propertyName + "' is expected to be <= " + max + " but is " + propValue);
}
if (step != null && propValue % step != 0) { if (step != null && propValue % step != 0) {
result.add(propertyName + "' is expected to be multiple of " + step + " but is " + propValue); result.add(propertyName + "' is expected to be multiple of " + step + " but is " + propValue);
} }
if (minFrom != null) {
validateMin(result, propertyName, propValue, propProvider.getContextValue(minFrom, Integer.class));
}
if (maxFrom != null) {
validateMax(result, propertyName, propValue, propProvider.getContextValue(maxFrom, Integer.class, 0));
}
} }
@Override @Override
protected String simpleTypeName() { protected String simpleTypeName() {
return "integer"; return "integer";
} }
private static void validateMin(final ArrayList<String> result, final String propertyName, final Integer propValue, final Integer min) {
if (min != null && propValue < min) {
result.add(propertyName + "' is expected to be at least " + min + " but is " + propValue);
}
}
private static void validateMax(final ArrayList<String> result, final String propertyName, final Integer propValue, final Integer max) {
if (max != null && propValue > max) {
result.add(propertyName + "' is expected to be at most " + max + " but is " + propValue);
}
}
} }

View File

@ -4,7 +4,6 @@ import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array; import net.hostsharing.hsadminng.mapper.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -57,7 +56,7 @@ public class StringProperty extends ValidatableProperty<String> {
} }
@Override @Override
protected void validate(final ArrayList<String> result, final String propValue, final Map<String, Object> props) { protected void validate(final ArrayList<String> result, final String propValue, final PropertiesProvider propProvider) {
if (minLength != null && propValue.length()<minLength) { if (minLength != null && propValue.length()<minLength) {
result.add(propertyName + "' length is expected to be at min " + minLength + " but length of '" + propValue+ "' is " + propValue.length()); result.add(propertyName + "' length is expected to be at min " + minLength + " but length of '" + propValue+ "' is " + propValue.length());
} }
@ -67,6 +66,9 @@ public class StringProperty extends ValidatableProperty<String> {
if (regExPattern != null && !regExPattern.matcher(propValue).matches()) { if (regExPattern != null && !regExPattern.matcher(propValue).matches()) {
result.add(propertyName + "' is expected to be match " + regExPattern + " but '" + propValue+ "' does not match"); result.add(propertyName + "' is expected to be match " + regExPattern + " but '" + propValue+ "' does not match");
} }
if (readOnly && propValue != null) {
result.add(propertyName + "' is readonly but given as '" + propValue+ "'");
}
} }
@Override @Override

View File

@ -116,8 +116,9 @@ public abstract class ValidatableProperty<T> {
return this; return this;
} }
public final List<String> validate(final Map<String, Object> props) { public final List<String> validate(final PropertiesProvider propsProvider) {
final var result = new ArrayList<String>(); final var result = new ArrayList<String>();
final var props = propsProvider.directProps();
final var propValue = props.get(propertyName); final var propValue = props.get(propertyName);
if (propValue == null) { if (propValue == null) {
if (required) { if (required) {
@ -127,7 +128,7 @@ public abstract class ValidatableProperty<T> {
if (propValue != null){ if (propValue != null){
if ( type.isInstance(propValue)) { if ( type.isInstance(propValue)) {
//noinspection unchecked //noinspection unchecked
validate(result, (T) propValue, props); validate(result, (T) propValue, propsProvider);
} else { } else {
result.add(propertyName + "' is expected to be of type " + type + ", " + result.add(propertyName + "' is expected to be of type " + type + ", " +
"but is of type '" + propValue.getClass().getSimpleName() + "'"); "but is of type '" + propValue.getClass().getSimpleName() + "'");
@ -136,7 +137,7 @@ public abstract class ValidatableProperty<T> {
return result; return result;
} }
protected abstract void validate(final ArrayList<String> result, final T propValue, final Map<String, Object> props); protected abstract void validate(final ArrayList<String> result, final T propValue, final PropertiesProvider propProvider);
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) { public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
if (required == null ) { if (required == null ) {

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.mapper; package net.hostsharing.hsadminng.mapper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -43,4 +44,8 @@ public class Array {
.toArray(String[]::new); .toArray(String[]::new);
return joined; return joined;
} }
public static <T> T[] emptyArray() {
return of();
}
} }

View File

@ -37,7 +37,7 @@ public class TestHsBookingItem {
.type(HsBookingItemType.MANAGED_WEBSPACE) .type(HsBookingItemType.MANAGED_WEBSPACE)
.caption("test managed webspace item") .caption("test managed webspace item")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("SSD", 25), entry("SSD", 50),
entry("Traffic", 250) entry("Traffic", 250)
)) ))
.validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15)))

View File

@ -292,8 +292,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"statusPhrase": "Bad Request", "statusPhrase": "Bad Request",
"message": "[ "message": "[
<<<'MANAGED_SERVER:vm1400.config.extra' is not expected but is set to '42', <<<'MANAGED_SERVER:vm1400.config.extra' is not expected but is set to '42',
<<<'MANAGED_SERVER:vm1400.config.monit_max_cpu_usage' is expected to be <= 100 but is 101, <<<'MANAGED_SERVER:vm1400.config.monit_max_cpu_usage' is expected to be at most 100 but is 101,
<<<'MANAGED_SERVER:vm1400.config.monit_max_ssd_usage' is expected to be >= 10 but is 0 <<<'MANAGED_SERVER:vm1400.config.monit_max_ssd_usage' is expected to be at least 10 but is 0
<<<]" <<<]"
} }
""".replaceAll(" +<<<", ""))); // @formatter:on """.replaceAll(" +<<<", ""))); // @formatter:on

View File

@ -37,8 +37,8 @@ class HsManagedServerHostingAssetValidatorUnitTest {
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-???????-?:null", "'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-???????-?:null",
"'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-???????-?:null", "'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-???????-?:null",
"'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be >= 10 but is 2", "'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be at least 10 but is 2",
"'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be <= 100 but is 101", "'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be at most 100 but is 101",
"'MANAGED_SERVER:vm1234.config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'"); "'MANAGED_SERVER:vm1234.config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'");
} }

View File

@ -36,6 +36,12 @@ class HsUnixUserHostingAssetValidatorUnitTest {
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET) .parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
.identifier("abc00-temp") .identifier("abc00-temp")
.caption("some valid test UnixUser") .caption("some valid test UnixUser")
.config(Map.ofEntries(
entry("SSD hard quota", 50),
entry("SSD soft quota", 40),
entry("totpKey", "0x123456789abcdef01234"),
entry("password", "Hallo Computer, lass mich rein!")
))
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
@ -55,13 +61,14 @@ class HsUnixUserHostingAssetValidatorUnitTest {
.identifier("abc00-temp") .identifier("abc00-temp")
.caption("some test UnixUser with invalid properties") .caption("some test UnixUser with invalid properties")
.config(Map.ofEntries( .config(Map.ofEntries(
entry("SSD hard quota", 1000), entry("SSD hard quota", 100),
entry("SSD soft quota", 2000), entry("SSD soft quota", 200),
entry("HDD hard quota", 1000), entry("HDD hard quota", 100),
entry("HDD soft quota", 2000), entry("HDD soft quota", 200),
entry("shell", "/is/invalid"),
entry("homedir", "/is/read-only"), entry("homedir", "/is/read-only"),
entry("totpKey", "should be a hex number"), entry("totpKey", "should be a hex number"),
entry("password", "should be a hex number") entry("password", "short")
)) ))
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
@ -70,7 +77,16 @@ class HsUnixUserHostingAssetValidatorUnitTest {
final var result = validator.validate(unixUserHostingAsset); final var result = validator.validate(unixUserHostingAsset);
// then // then
assertThat(result).isEmpty(); assertThat(result).containsExactlyInAnyOrder(
"'UNIX_USER:abc00-temp.config.SSD hard quota' is expected to be at most 50 but is 100",
"'UNIX_USER:abc00-temp.config.SSD soft quota' is expected to be at most 100 but is 200",
"'UNIX_USER:abc00-temp.config.HDD hard quota' is expected to be at most 0 but is 100",
"'UNIX_USER:abc00-temp.config.HDD soft quota' is expected to be at most 100 but is 200",
"'UNIX_USER:abc00-temp.config.shell' is expected to be one of [/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd] but is '/is/invalid'",
hsh-michaelhoennig marked this conversation as resolved
Review

evtl. nur false, bash, csh, ...

evtl. nur false, bash, csh, ...
"'UNIX_USER:abc00-temp.config.homedir' is readonly but given as '/is/read-only'",
"'UNIX_USER:abc00-temp.config.totpKey' is expected to be match ^0x([0-9A-Fa-f]{2})+$ but 'should be a hex number' does not match",
"'UNIX_USER:abc00-temp.config.password' length is expected to be at min 8 but length of 'short' is 5"
);
} }
@Test @Test