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
7 changed files with 51 additions and 26 deletions
Showing only changes of commit c59fb34b8b - Show all commits

View File

@ -29,7 +29,7 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
.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().computedBy(HsUnixUserHostingAssetValidator::computeHomedir), stringProperty("homedir").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeHomedir),
stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).hidden().writeOnly().optional(), stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).undisclosed().writeOnly().optional(),
passwordProperty("password").minLength(8).maxLength(40).writeOnly()); passwordProperty("password").minLength(8).maxLength(40).writeOnly());
} }

View File

@ -32,20 +32,20 @@ public class EnumerationProperty extends ValidatableProperty<String> {
} }
public void deferredInit(final ValidatableProperty<?>[] allProperties) { public void deferredInit(final ValidatableProperty<?>[] allProperties) {
if (deferredInit != null) { if (hasDeferredInit()) {
if (this.values != null) { if (this.values != null) {
throw new IllegalStateException("property " + this + " already has values"); throw new IllegalStateException("property " + this + " already has values");
} }
this.values = deferredInit.apply(allProperties); this.values = doDeferredInit(allProperties);
} }
} }
public ValidatableProperty<String> valuesFromProperties(final String propertyNamePrefix) { public ValidatableProperty<String> valuesFromProperties(final String propertyNamePrefix) {
this.deferredInit = (ValidatableProperty<?>[] allProperties) -> stream(allProperties) this.setDeferredInit( (ValidatableProperty<?>[] allProperties) -> stream(allProperties)
.map(ValidatableProperty::propertyName) .map(ValidatableProperty::propertyName)
.filter(name -> name.startsWith(propertyNamePrefix)) .filter(name -> name.startsWith(propertyNamePrefix))
.map(name -> name.substring(propertyNamePrefix.length())) .map(name -> name.substring(propertyNamePrefix.length()))
.toArray(String[]::new); .toArray(String[]::new));
return this; return this;
} }

View File

@ -92,7 +92,7 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
public Map<String, Object> postProcess(final E entity, final Map<String, Object> config) { public Map<String, Object> postProcess(final E entity, final Map<String, Object> config) {
final var copy = new HashMap<>(config); final var copy = new HashMap<>(config);
stream(propertyValidators).forEach(p -> { stream(propertyValidators).forEach(p -> {
if ( p.writeOnly) { if ( p.isWriteOnly()) {
copy.remove(p.propertyName); copy.remove(p.propertyName);
} }
if (p.isComputed()) { if (p.isComputed()) {

View File

@ -10,7 +10,7 @@ public class PasswordProperty extends StringProperty {
private PasswordProperty(final String propertyName) { private PasswordProperty(final String propertyName) {
super(propertyName); super(propertyName);
hidden(); undisclosed();
} }
public static PasswordProperty passwordProperty(final String propertyName) { public static PasswordProperty passwordProperty(final String propertyName) {

View File

@ -6,7 +6,6 @@ import net.hostsharing.hsadminng.mapper.Array;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@Setter @Setter
public class StringProperty extends ValidatableProperty<String> { public class StringProperty extends ValidatableProperty<String> {
@ -14,11 +13,11 @@ public class StringProperty extends ValidatableProperty<String> {
ValidatableProperty.KEY_ORDER_HEAD, ValidatableProperty.KEY_ORDER_HEAD,
Array.of("matchesRegEx", "minLength", "maxLength"), Array.of("matchesRegEx", "minLength", "maxLength"),
ValidatableProperty.KEY_ORDER_TAIL, ValidatableProperty.KEY_ORDER_TAIL,
Array.of("hidden")); Array.of("undisclosed"));
private Pattern matchesRegEx; private Pattern matchesRegEx;
private Integer minLength; private Integer minLength;
private Integer maxLength; private Integer maxLength;
private boolean hidden; private boolean undisclosed;
protected StringProperty(final String propertyName) { protected StringProperty(final String propertyName) {
super(String.class, propertyName, KEY_ORDER); super(String.class, propertyName, KEY_ORDER);
@ -43,8 +42,13 @@ public class StringProperty extends ValidatableProperty<String> {
return this; return this;
} }
public StringProperty hidden() { /**
this.hidden = true; * The property value is not disclosed in error messages.
*
* @return this;
*/
public StringProperty undisclosed() {
this.undisclosed = true;
return this; return this;
} }
@ -59,13 +63,13 @@ public class StringProperty extends ValidatableProperty<String> {
if (matchesRegEx != null && !matchesRegEx.matcher(propValue).matches()) { if (matchesRegEx != null && !matchesRegEx.matcher(propValue).matches()) {
result.add(propertyName + "' is expected to be match " + matchesRegEx + " but " + display(propValue) + " does not match"); result.add(propertyName + "' is expected to be match " + matchesRegEx + " but " + display(propValue) + " does not match");
} }
if (readOnly && propValue != null) { if (isReadOnly() && propValue != null) {
result.add(propertyName + "' is readonly but given as " + display(propValue)); result.add(propertyName + "' is readonly but given as " + display(propValue));
} }
} }
private String display(final String propValue) { private String display(final String propValue) {
return hidden ? "provided value" : ("'" + propValue + "'"); return undisclosed ? "provided value" : ("'" + propValue + "'");
} }
@Override @Override

View File

@ -1,6 +1,8 @@
package net.hostsharing.hsadminng.hs.validation; package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.experimental.Accessors;
import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
@ -21,6 +23,7 @@ import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
@Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class ValidatableProperty<T> { public abstract class ValidatableProperty<T> {
@ -29,16 +32,28 @@ public abstract class ValidatableProperty<T> {
final Class<T> type; final Class<T> type;
final String propertyName; final String propertyName;
@JsonIgnore
private final String[] keyOrder; private final String[] keyOrder;
private Boolean required; private Boolean required;
private T defaultValue; private T defaultValue;
protected Function<PropertiesProvider, T> computedBy;
protected boolean computed; // used in descriptor, because computedBy cannot be rendered to a text string
protected boolean readOnly;
protected boolean writeOnly;
protected Function<ValidatableProperty<?>[], T[]> deferredInit; @JsonIgnore
private Function<PropertiesProvider, T> computedBy;
@Accessors(makeFinal = true, chain = true, fluent = false)
private boolean computed; // used in descriptor, because computedBy cannot be rendered to a text string
@Accessors(makeFinal = true, chain = true, fluent = false)
private boolean readOnly;
@Accessors(makeFinal = true, chain = true, fluent = false)
private boolean writeOnly;
private Function<ValidatableProperty<?>[], T[]> deferredInit;
private boolean isTotalsValidator = false; private boolean isTotalsValidator = false;
@JsonIgnore @JsonIgnore
private List<Function<HsBookingItemEntity, List<String>>> asTotalLimitValidators; // TODO.impl: move to BookingItemIntegerProperty private List<Function<HsBookingItemEntity, List<String>>> asTotalLimitValidators; // TODO.impl: move to BookingItemIntegerProperty
@ -48,6 +63,17 @@ public abstract class ValidatableProperty<T> {
return null; return null;
} }
protected void setDeferredInit(final Function<ValidatableProperty<?>[], T[]> function) {
this.deferredInit = function;
}
public boolean hasDeferredInit() {
return deferredInit != null;
}
public T[] doDeferredInit(final ValidatableProperty<?>[] allProperties) {
return deferredInit.apply(allProperties);
}
public ValidatableProperty<T> writeOnly() { public ValidatableProperty<T> writeOnly() {
this.writeOnly = true; this.writeOnly = true;
@ -61,7 +87,6 @@ public abstract class ValidatableProperty<T> {
return this; return this;
} }
public ValidatableProperty<T> required() { public ValidatableProperty<T> required() {
required = TRUE; required = TRUE;
return this; return this;
@ -231,10 +256,6 @@ public abstract class ValidatableProperty<T> {
return this; return this;
} }
public boolean isComputed() {
return computedBy != null;
}
public <E extends PropertiesProvider> T compute(final E entity) { public <E extends PropertiesProvider> T compute(final E entity) {
return computedBy.apply(entity); return computedBy.apply(entity);
} }

View File

@ -124,8 +124,8 @@ class HsUnixUserHostingAssetValidatorUnitTest {
"{type=integer, propertyName=HDD soft quota, unit=GB, maxFrom=HDD hard quota}", "{type=integer, propertyName=HDD soft quota, unit=GB, maxFrom=HDD hard quota}",
"{type=enumeration, propertyName=shell, values=[/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd], defaultValue=/bin/false}", "{type=enumeration, propertyName=shell, values=[/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd], defaultValue=/bin/false}",
"{type=string, propertyName=homedir, readOnly=true, computed=true}", "{type=string, propertyName=homedir, readOnly=true, computed=true}",
"{type=string, propertyName=totpKey, matchesRegEx=^0x([0-9A-Fa-f]{2})+$, minLength=20, maxLength=256, writeOnly=true, hidden=true}", "{type=string, propertyName=totpKey, matchesRegEx=^0x([0-9A-Fa-f]{2})+$, minLength=20, maxLength=256, writeOnly=true, undisclosed=true}",
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, hidden=true}" "{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, undisclosed=true}"
); );
} }
} }