1
0

add-email-alias-hosting-asset (#70)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/70
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-07-03 11:43:08 +02:00
parent c5722e494f
commit a77eaefb94
16 changed files with 524 additions and 46 deletions

View File

@ -159,6 +159,6 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
@SuppressWarnings("unchecked")
final BiConsumer<HsHostingAssetEntity, HsHostingAssetResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource)
-> HsHostingAssetEntityValidatorRegistry.forType(entity.getType())
.revampProperties(entity, (Map<String, Object>) resource.getConfig());
-> resource.setConfig(HsHostingAssetEntityValidatorRegistry.forType(entity.getType())
.revampProperties(entity, (Map<String, Object>) resource.getConfig()));
}

View File

@ -0,0 +1,33 @@
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 java.util.regex.Pattern;
import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf;
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
class HsEMailAliasHostingAssetValidator extends HsHostingAssetEntityValidator {
private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$"; // also accepts legacy pac-names
private static final String EMAIL_ADDRESS_REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"; // RFC 5322
public static final int EMAIL_ADDRESS_MAX_LENGTH = 320; // according to RFC 5321 and RFC 5322
HsEMailAliasHostingAssetValidator() {
super( BookingItem.mustBeNull(),
ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE),
AssignedToAsset.mustBeNull(),
AlarmContact.isOptional(),
arrayOf(
stringProperty("target").maxLength(EMAIL_ADDRESS_MAX_LENGTH).matchesRegEx(UNIX_USER_REGEX, EMAIL_ADDRESS_REGEX)
).required().minLength(1));
}
@Override
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9]+$");
}
}

View File

@ -19,6 +19,7 @@ public class HsHostingAssetEntityValidatorRegistry {
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
register(UNIX_USER, new HsUnixUserHostingAssetValidator());
register(EMAIL_ALIAS, new HsEMailAliasHostingAssetValidator());
}
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity> validator) {

View File

@ -0,0 +1,63 @@
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import java.util.Arrays;
import java.util.List;
import static java.util.Arrays.stream;
import static net.hostsharing.hsadminng.mapper.Array.insertNewEntriesAfterExistingEntry;
@Setter
public class ArrayProperty<P extends ValidatableProperty<?, E>, E> extends ValidatableProperty<ArrayProperty<P, E>, E[]> {
private static final String[] KEY_ORDER =
insertNewEntriesAfterExistingEntry(
insertNewEntriesAfterExistingEntry(ValidatableProperty.KEY_ORDER, "required", "minLength" ,"maxLength"),
"propertyName", "elementsOf");
private final ValidatableProperty<?, E> elementsOf;
private Integer minLength;
private Integer maxLength;
private ArrayProperty(final ValidatableProperty<?, E> elementsOf) {
//noinspection unchecked
super((Class<E[]>) elementsOf.type.arrayType(), elementsOf.propertyName, KEY_ORDER);
this.elementsOf = elementsOf;
}
public static <T> ArrayProperty<?, T[]> arrayOf(final ValidatableProperty<?, T> elementsOf) {
//noinspection unchecked
return (ArrayProperty<?, T[]>) new ArrayProperty<>(elementsOf);
}
public ValidatableProperty<?, ?> minLength(final int minLength) {
this.minLength = minLength;
return self();
}
public ValidatableProperty<?, ?> maxLength(final int maxLength) {
this.maxLength = maxLength;
return self();
}
@Override
protected void validate(final List<String> result, final E[] propValue, final PropertiesProvider propProvider) {
if (minLength != null && propValue.length < minLength) {
result.add(propertyName + "' length is expected to be at min " + minLength + " but length of " + display(propValue) + " is " + propValue.length);
}
if (maxLength != null && propValue.length > maxLength) {
result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of " + display(propValue) + " is " + propValue.length);
}
stream(propValue).forEach(e -> elementsOf.validate(result, e, propProvider));
}
@Override
protected String simpleTypeName() {
return elementsOf.simpleTypeName() + "[]";
}
@SafeVarargs
private String display(final E... propValue) {
return "[" + Arrays.toString(propValue) + "]";
}
}

View File

@ -8,12 +8,12 @@ import java.util.stream.Stream;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.hash;
import static net.hostsharing.hsadminng.mapper.Array.insertAfterEntry;
import static net.hostsharing.hsadminng.mapper.Array.insertNewEntriesAfterExistingEntry;
@Setter
public class PasswordProperty extends StringProperty<PasswordProperty> {
private static final String[] KEY_ORDER = insertAfterEntry(StringProperty.KEY_ORDER, "computed", "hashedUsing");
private static final String[] KEY_ORDER = insertNewEntriesAfterExistingEntry(StringProperty.KEY_ORDER, "computed", "hashedUsing");
private Algorithm hashedUsing;

View File

@ -3,9 +3,12 @@ package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import net.hostsharing.hsadminng.mapper.Array;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.Arrays.stream;
@Setter
public class StringProperty<P extends StringProperty<P>> extends ValidatableProperty<P, String> {
@ -15,7 +18,7 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
Array.of("matchesRegEx", "minLength", "maxLength"),
ValidatableProperty.KEY_ORDER_TAIL,
Array.of("undisclosed"));
private Pattern matchesRegEx;
private Pattern[] matchesRegEx;
private Integer minLength;
private Integer maxLength;
private boolean undisclosed;
@ -42,8 +45,8 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
return self();
}
public P matchesRegEx(final String regExPattern) {
this.matchesRegEx = Pattern.compile(regExPattern);
public P matchesRegEx(final String... regExPattern) {
this.matchesRegEx = stream(regExPattern).map(Pattern::compile).toArray(Pattern[]::new);
return self();
}
@ -65,8 +68,9 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
if (maxLength != null && propValue.length()>maxLength) {
result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of " + display(propValue) + " is " + propValue.length());
}
if (matchesRegEx != null && !matchesRegEx.matcher(propValue).matches()) {
result.add(propertyName + "' is expected to be match " + matchesRegEx + " but " + display(propValue) + " does not match");
if (matchesRegEx != null &&
stream(matchesRegEx).map(p -> p.matcher(propValue)).noneMatch(Matcher::matches)) {
result.add(propertyName + "' is expected to match any of " + Arrays.toString(matchesRegEx) + " but " + display(propValue) + " does not match any");
}
if (isReadOnly() && propValue != null) {
result.add(propertyName + "' is readonly but given as " + display(propValue));

View File

@ -1,15 +1,16 @@
package net.hostsharing.hsadminng.hs.validation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.experimental.Accessors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.mapper.Array;
import org.apache.commons.lang3.function.TriFunction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
@ -22,6 +23,7 @@ import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.ObjectUtils.isArray;
@Getter
@RequiredArgsConstructor
@ -29,6 +31,7 @@ public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T
protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName");
protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage");
protected static final String[] KEY_ORDER = Array.join(KEY_ORDER_HEAD, KEY_ORDER_TAIL);
final Class<T> type;
final String propertyName;
@ -238,8 +241,8 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
}
private Object arrayToList(final Object value) {
if ( value instanceof String[]) {
return List.of((String[])value);
if (isArray(value)) {
return Arrays.stream((Object[])value).map(Object::toString).toList();
}
return value;
}
@ -264,4 +267,9 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
public <E extends PropertiesProvider> T compute(final E entity) {
return computedBy.apply(entity);
}
@Override
public String toString() {
return toOrderedMap().toString();
}
}

View File

@ -51,13 +51,16 @@ public class Array {
return of();
}
public static <T> T[] insertAfterEntry(final T[] array, final T entryToFind, final T newEntry) {
@SafeVarargs
public static <T> T[] insertNewEntriesAfterExistingEntry(final T[] array, final T entryToFind, final T... newEntries) {
final var arrayList = new ArrayList<>(asList(array));
final var index = arrayList.indexOf(entryToFind);
if (index < 0) {
throw new IllegalArgumentException("entry "+ entryToFind + " not found in " + Arrays.toString(array));
}
arrayList.add(index + 1, newEntry);
for (int n = 0; n < newEntries.length; ++n) {
arrayList.add(index +n + 1, newEntries[n]);
}
@SuppressWarnings("unchecked")
final var extendedArray = (T[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), array.length);