add-unix-user-hosting-asset-validation #66
@ -11,6 +11,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||
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.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||
@ -42,6 +43,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||
@ -68,7 +70,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
||||
public class HsBookingItemEntity implements Stringifyable, RbacObject, PropertiesProvider {
|
||||
|
||||
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
|
||||
.withProp(HsBookingItemEntity::getProject)
|
||||
@ -146,6 +148,23 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
||||
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
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
|
@ -29,7 +29,7 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -9,6 +9,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
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.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||
@ -39,6 +40,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
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.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
||||
@ -63,7 +65,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject {
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject, PropertiesProvider {
|
||||
|
||||
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
||||
.withProp(HsHostingAssetEntity::getType)
|
||||
@ -122,7 +124,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
|
||||
private PatchableMapWrapper<Object> configWrapper;
|
||||
|
||||
@Transient
|
||||
private boolean isLoaded = false;
|
||||
private boolean isLoaded;
|
||||
|
||||
@PostLoad
|
||||
public void markAsLoaded() {
|
||||
@ -137,6 +139,28 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
|
||||
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
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
|
@ -76,7 +76,7 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -18,14 +18,14 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
|
||||
AlarmContact.isOptional(),
|
||||
|
||||
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 soft quota").unit("GB").minFrom("HDD hard quota").optional(),
|
||||
integerProperty("HDD soft quota").unit("GB").maxFrom("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("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).writeOnly().optional(),
|
||||
stringProperty("password").minLength(8).maxLength(40).writeOnly()); // FIXME: spec
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,9 @@ public class BooleanProperty extends ValidatableProperty<Boolean> {
|
||||
}
|
||||
|
||||
@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) {
|
||||
final Object referencedValue = props.get(falseIf.getKey());
|
||||
final Object referencedValue = propProvider.directProps().get(falseIf.getKey());
|
||||
if (Objects.equals(referencedValue, falseIf.getValue())) {
|
||||
result.add(propertyName + "' is expected to be false because " +
|
||||
falseIf.getKey() + "=" + referencedValue + " but is " + propValue);
|
||||
|
@ -5,7 +5,6 @@ import net.hostsharing.hsadminng.mapper.Array;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
|
||||
@ -51,7 +50,7 @@ public class EnumerationProperty extends ValidatableProperty<String> {
|
||||
}
|
||||
|
||||
@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))) {
|
||||
result.add(propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
|
||||
}
|
||||
|
@ -38,10 +38,11 @@ public abstract class HsEntityValidator<E> {
|
||||
.toList();
|
||||
}
|
||||
|
||||
protected ArrayList<String> validateProperties(final Map<String, Object> properties) {
|
||||
protected ArrayList<String> validateProperties(final PropertiesProvider propsProvider) {
|
||||
final var result = new ArrayList<String>();
|
||||
|
||||
// verify that all actually given properties are specified
|
||||
final var properties = propsProvider.directProps();
|
||||
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) + "'");
|
||||
@ -50,7 +51,7 @@ public abstract class HsEntityValidator<E> {
|
||||
|
||||
// run all property validators
|
||||
stream(propertyValidators).forEach(pv -> {
|
||||
result.addAll(pv.validate(properties));
|
||||
result.addAll(pv.validate(propsProvider));
|
||||
});
|
||||
|
||||
return result;
|
||||
|
@ -2,9 +2,9 @@ package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.mapper.Array;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
@Setter
|
||||
public class IntegerProperty extends ValidatableProperty<Integer> {
|
||||
@ -29,6 +29,12 @@ public class IntegerProperty extends ValidatableProperty<Integer> {
|
||||
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) {
|
||||
minFrom = propertyName;
|
||||
return this;
|
||||
@ -49,20 +55,34 @@ public class IntegerProperty extends ValidatableProperty<Integer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validate(final ArrayList<String> result, final Integer propValue, final Map<String, Object> props) {
|
||||
if (min != null && propValue < min) {
|
||||
result.add(propertyName + "' is expected to be >= " + min + " but is " + propValue);
|
||||
}
|
||||
if (max != null && propValue > max) {
|
||||
result.add(propertyName + "' is expected to be <= " + max + " but is " + propValue);
|
||||
}
|
||||
protected void validate(final ArrayList<String> result, final Integer propValue, final PropertiesProvider propProvider) {
|
||||
validateMin(result, propertyName, propValue, min);
|
||||
validateMax(result, propertyName, propValue, max);
|
||||
if (step != null && propValue % step != 0) {
|
||||
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
|
||||
protected String simpleTypeName() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.mapper.Array;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
@ -57,7 +56,7 @@ public class StringProperty extends ValidatableProperty<String> {
|
||||
}
|
||||
|
||||
@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) {
|
||||
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()) {
|
||||
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
|
||||
|
@ -116,8 +116,9 @@ public abstract class ValidatableProperty<T> {
|
||||
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 props = propsProvider.directProps();
|
||||
final var propValue = props.get(propertyName);
|
||||
if (propValue == null) {
|
||||
if (required) {
|
||||
@ -127,7 +128,7 @@ public abstract class ValidatableProperty<T> {
|
||||
if (propValue != null){
|
||||
if ( type.isInstance(propValue)) {
|
||||
//noinspection unchecked
|
||||
validate(result, (T) propValue, props);
|
||||
validate(result, (T) propValue, propsProvider);
|
||||
} else {
|
||||
result.add(propertyName + "' is expected to be of type " + type + ", " +
|
||||
"but is of type '" + propValue.getClass().getSimpleName() + "'");
|
||||
@ -136,7 +137,7 @@ public abstract class ValidatableProperty<T> {
|
||||
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) {
|
||||
if (required == null ) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.hostsharing.hsadminng.mapper;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -43,4 +44,8 @@ public class Array {
|
||||
.toArray(String[]::new);
|
||||
return joined;
|
||||
}
|
||||
|
||||
public static <T> T[] emptyArray() {
|
||||
return of();
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public class TestHsBookingItem {
|
||||
.type(HsBookingItemType.MANAGED_WEBSPACE)
|
||||
.caption("test managed webspace item")
|
||||
.resources(Map.ofEntries(
|
||||
entry("SSD", 25),
|
||||
entry("SSD", 50),
|
||||
entry("Traffic", 250)
|
||||
))
|
||||
.validity(Range.closedInfinite(LocalDate.of(2020, 1, 15)))
|
||||
|
@ -292,8 +292,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
"statusPhrase": "Bad Request",
|
||||
"message": "[
|
||||
<<<'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_ssd_usage' is expected to be >= 10 but is 0
|
||||
<<<'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 at least 10 but is 0
|
||||
<<<]"
|
||||
}
|
||||
""".replaceAll(" +<<<", ""))); // @formatter:on
|
||||
|
@ -37,8 +37,8 @@ class HsManagedServerHostingAssetValidatorUnitTest {
|
||||
assertThat(result).containsExactlyInAnyOrder(
|
||||
"'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.config.monit_max_cpu_usage' is expected to be >= 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_cpu_usage' is expected to be at least 10 but is 2",
|
||||
"'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'");
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,12 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
|
||||
.identifier("abc00-temp")
|
||||
.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();
|
||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||
|
||||
@ -55,13 +61,14 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
.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("SSD hard quota", 100),
|
||||
entry("SSD soft quota", 200),
|
||||
entry("HDD hard quota", 100),
|
||||
entry("HDD soft quota", 200),
|
||||
entry("shell", "/is/invalid"),
|
||||
entry("homedir", "/is/read-only"),
|
||||
entry("totpKey", "should be a hex number"),
|
||||
entry("password", "should be a hex number")
|
||||
entry("password", "short")
|
||||
))
|
||||
.build();
|
||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||
@ -70,7 +77,16 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
final var result = validator.validate(unixUserHostingAsset);
|
||||
|
||||
// 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
|
||||
"'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
|
||||
|
Loading…
Reference in New Issue
Block a user
evtl. nur false, bash, csh, ...