Compare commits

...

3 Commits

Author SHA1 Message Date
Michael Hoennig
5521cc13f8 implements EMailAlias-Property with ArrayProperty+multi-RegEx 2024-07-02 16:09:51 +02:00
Michael Hoennig
c19360aa77 add EMail-Alias hosting asset validation (WIP) 2024-07-02 13:29:25 +02:00
Michael Hoennig
278467efac fix HsHostingAssetRepository.findAllByCriteriaImpl query 2024-07-02 13:18:50 +02:00
16 changed files with 299 additions and 76 deletions

View File

@ -14,12 +14,26 @@ public interface HsHostingAssetRepository extends Repository<HsHostingAssetEntit
List<HsHostingAssetEntity> findByIdentifier(String assetIdentifier);
@Query("""
SELECT asset FROM HsHostingAssetEntity asset
WHERE (:projectUuid IS NULL OR asset.bookingItem.project.uuid = :projectUuid)
AND (:parentAssetUuid IS NULL OR asset.parentAsset.uuid = :parentAssetUuid)
AND (:type IS NULL OR :type = CAST(asset.type AS String))
""")
@Query(value = """
select ha.uuid,
ha.alarmcontactuuid,
ha.assignedtoassetuuid,
ha.bookingitemuuid,
ha.caption,
ha.config,
ha.identifier,
ha.parentassetuuid,
ha.type,
ha.version
from hs_hosting_asset_rv ha
left join hs_booking_item bi on bi.uuid = ha.bookingitemuuid
left join hs_hosting_asset pha on pha.uuid = ha.parentassetuuid
where (:projectUuid is null or bi.projectuuid=:projectUuid)
and (:parentAssetUuid is null or pha.uuid=:parentAssetUuid)
and (:type is null or :type=cast(ha.type as text))
""", nativeQuery = true)
// The JPQL query did not generate "left join" but just "join".
// I also optimized the query by not using the _rv for hs_booking_item and hs_hosting_asset, only for hs_hosting_asset_rv.
List<HsHostingAssetEntity> findAllByCriteriaImpl(UUID projectUuid, UUID parentAssetUuid, String type);
default List<HsHostingAssetEntity> findAllByCriteria(final UUID projectUuid, final UUID parentAssetUuid, final HsHostingAssetType type) {
return findAllByCriteriaImpl(projectUuid, parentAssetUuid, HsHostingAssetType.asString(type));

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]{3}[0-9]{2}(-[a-z0-9]+)?$";
private static final String EMAIL_ADDRESS_REGEX = "^[a-zA-Z0-9_.±]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$";
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.insertAfterEntries;
@Setter
public class ArrayProperty<P extends ValidatableProperty<?, E>, E> extends ValidatableProperty<ArrayProperty<P, E>, E[]> {
private static final String[] KEY_ORDER =
insertAfterEntries(
insertAfterEntries(ValidatableProperty.KEY_ORDER, "required", "minLength" ,"maxLength"),
"propertyName", "elementProperty");
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.insertAfterEntries;
@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 = insertAfterEntries(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[] insertAfterEntries(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);

View File

@ -7,13 +7,13 @@ get:
parameters:
- $ref: 'auth.yaml#/components/parameters/currentUser'
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: debitorUuid
- name: projectUuid
in: query
required: false
schema:
type: string
format: uuid
description: The UUID of the debitor, whose hosting assets are to be listed.
description: The UUID of the project, whose hosting assets are to be listed.
- name: parentAssetUuid
in: query
required: false

View File

@ -73,6 +73,7 @@ begin
values (managedServerUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "monit_max_cpu_usage": 90, "monit_max_ram_usage": 80, "monit_max_ssd_usage": 70 }'::jsonb),
(uuid_generate_v4(), relatedCloudServerBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{}'::jsonb),
(managedWebspaceUuid, relatedManagedWebspaceBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{}'::jsonb),
(uuid_generate_v4(), null, 'EMAIL_ALIAS', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some E-Mail-Alias', '{ "target": [ "office@example.org", "archive@example.com" ] }'::jsonb),
(webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024"}'::jsonb),
(uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', managedWebspaceUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*"}'::jsonb);
end; $$;

View File

@ -29,6 +29,7 @@ import java.util.UUID;
import java.util.function.Supplier;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ALIAS;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
@ -89,22 +90,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType("application/json")
.body("", lenientlyEquals("""
[
{
"type": "MANAGED_WEBSPACE",
"identifier": "sec01",
"caption": "some Webspace",
"config": {}
},
{
"type": "MANAGED_WEBSPACE",
"identifier": "fir01",
"caption": "some Webspace",
"config": {}
},
{
"type": "MANAGED_WEBSPACE",
"identifier": "thi01",
"caption": "some Webspace",
"caption": "some Webspace",
"config": {}
}
]
@ -113,7 +102,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
}
@Test
void globalAdmin_canViewAllAssetsByType() {
void webspaceAgent_canViewAllAssetsByType() {
// given
context("superuser-alex@hostsharing.net");
@ -121,42 +110,25 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_hosting_asset#fir01:AGENT")
.port(port)
.when()
. get("http://localhost/api/hs/hosting/assets?type=" + MANAGED_SERVER)
. get("http://localhost/api/hs/hosting/assets?type=" + EMAIL_ALIAS)
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
[
{
"type": "MANAGED_SERVER",
"identifier": "vm1011",
"caption": "some ManagedServer",
"type": "EMAIL_ALIAS",
"identifier": "fir01-web",
"caption": "some E-Mail-Alias",
"alarmContact": null,
"config": {
"monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
}
},
{
"type": "MANAGED_SERVER",
"identifier": "vm1012",
"caption": "some ManagedServer",
"config": {
"monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
}
},
{
"type": "MANAGED_SERVER",
"identifier": "vm1013",
"caption": "some ManagedServer",
"config": {
"monit_max_cpu_usage": 90,
"monit_max_ram_usage": 80,
"monit_max_ssd_usage": 70
"target": [
"office@example.org",
"archive@example.com"
]
}
}
]

View File

@ -34,7 +34,8 @@ class HsHostingAssetPropsControllerAcceptanceTest {
"MANAGED_SERVER",
"MANAGED_WEBSPACE",
"CLOUD_SERVER",
"UNIX_USER"
"UNIX_USER",
"EMAIL_ALIAS"
]
"""));
// @formatter:on

View File

@ -174,7 +174,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
final var result = assetRepo.findAllByCriteria(null, null, MANAGED_WEBSPACE);
// then
allTheseServersAreReturned(
exactlyTheseAssetsAreReturned(
result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedWebspace)",
"HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedWebspace)",
@ -202,18 +202,19 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
public void normalUser_canFilterAssetsRelatedToParentAsset() {
// given
context("superuser-alex@hostsharing.net");
final var parentAssetUuid = assetRepo.findAllByCriteria(null, null, MANAGED_SERVER).stream()
final var parentAssetUuid = assetRepo.findByIdentifier("vm1012").stream()
.filter(ha -> ha.getType() == MANAGED_SERVER)
.findAny().orElseThrow().getUuid();
// when
context("superuser-alex@hostsharing.net", "hs_hosting_asset#vm1012:AGENT");
final var result = assetRepo.findAllByCriteria(null, parentAssetUuid, null);
// then
allTheseServersAreReturned(
exactlyTheseAssetsAreReturned(
result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, sec01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:D-1000212 default project:separate ManagedWebspace)");
}
}
@Nested
@ -411,11 +412,4 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
.extracting(input -> input.replaceAll("\"", ""))
.containsExactlyInAnyOrder(serverNames);
}
void allTheseServersAreReturned(final List<HsHostingAssetEntity> actualResult, final String... serverNames) {
assertThat(actualResult)
.extracting(HsHostingAssetEntity::toString)
.contains(serverNames);
actualResult.forEach(loadedEntity -> assertThat(loadedEntity.isLoaded()).isTrue());
}
}

View File

@ -0,0 +1,128 @@
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 net.hostsharing.hsadminng.mapper.Array;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static java.util.Map.entry;
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.EMAIL_ALIAS;
import static org.assertj.core.api.Assertions.assertThat;
class HsEMailAliasHostingAssetValidatorUnitTest {
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 final HsHostingAssetEntity TEST_MANAGED_WEBSPACE_HOSTING_ASSET = HsHostingAssetEntity.builder()
.type(HsHostingAssetType.MANAGED_WEBSPACE)
.identifier("xyz00")
.caption("some managed webspace")
.bookingItem(TEST_MANAGED_WEBSPACE_BOOKING_ITEM)
.build();
@Test
void containsAllValidations() {
// when
final var validator = HsHostingAssetEntityValidatorRegistry.forType(EMAIL_ALIAS);
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=string[], propertyName=target, elementProperty={type=string, propertyName=target, matchesRegEx=[^[a-z]{3}[0-9]{2}(-[a-z0-9]+)?$, ^[a-zA-Z0-9_.±]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$], maxLength=320}, required=true, minLength=1}");
}
@Test
void validatesValidEntity() {
// given
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
.type(EMAIL_ALIAS)
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
.identifier("xyz00-office")
.config(Map.ofEntries(
entry("target", Array.of("xyz00", "xyz00-abc", "office@example.com"))
))
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(emailAliasHostingAssetEntity);
// then
assertThat(result).isEmpty();
}
@Test
void validatesProperties() {
// given
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
.type(EMAIL_ALIAS)
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
.identifier("xyz00-office")
.config(Map.ofEntries(
entry("target", Array.of("xyz00", "xyz00-abc", "garbage", "office@example.com"))
))
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(emailAliasHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'EMAIL_ALIAS:xyz00-office.config.target' is expected to match any of [^[a-z]{3}[0-9]{2}(-[a-z0-9]+)?$, ^[a-zA-Z0-9_.±]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$] but 'garbage' does not match any");
}
@Test
void validatesInvalidIdentifier() {
// given
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
.type(EMAIL_ALIAS)
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
.identifier("abc00-office")
.config(Map.ofEntries(
entry("target", Array.of("office@example.com"))
))
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(emailAliasHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'identifier' expected to match '^xyz00$|^xyz00-[a-z0-9]+$', but is 'abc00-office'");
}
@Test
void validatesInvalidReferences() {
// given
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
.type(EMAIL_ALIAS)
.bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM)
.parentAsset(TEST_MANAGED_SERVER_HOSTING_ASSET)
.assignedToAsset(TEST_MANAGED_SERVER_HOSTING_ASSET)
.identifier("abc00-office")
.config(Map.ofEntries(
entry("target", Array.of("office@example.com"))
))
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(emailAliasHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'EMAIL_ALIAS:abc00-office.bookingItem' must be null but is set to D-1234500:test project:test project booking item",
"'EMAIL_ALIAS:abc00-office.parentAsset' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER",
"'EMAIL_ALIAS:abc00-office.assignedToAsset' must be null but is set to D-1234500:test project:test project booking item");
}
}

View File

@ -32,7 +32,8 @@ class HsHostingAssetEntityValidatorRegistryUnitTest {
HsHostingAssetType.CLOUD_SERVER,
HsHostingAssetType.MANAGED_SERVER,
HsHostingAssetType.MANAGED_WEBSPACE,
HsHostingAssetType.UNIX_USER
HsHostingAssetType.UNIX_USER,
HsHostingAssetType.EMAIL_ALIAS
);
}
}

View File

@ -110,7 +110,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
"'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'",
"'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 provided value does not match",
"'UNIX_USER:abc00-temp.config.totpKey' is expected to match any of [^0x([0-9A-Fa-f]{2})+$] but provided value does not match any",
"'UNIX_USER:abc00-temp.config.password' length is expected to be at min 8 but length of provided value is 5",
"'UNIX_USER:abc00-temp.config.password' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters"
);
@ -168,7 +168,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
"{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=string, propertyName=homedir, readOnly=true, computed=true}",
"{type=string, propertyName=totpKey, matchesRegEx=^0x([0-9A-Fa-f]{2})+$, minLength=20, maxLength=256, writeOnly=true, undisclosed=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, computed=true, hashedUsing=SHA512, undisclosed=true}"
);
}