add-email-alias-hosting-asset (#70)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #70 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
parent
c5722e494f
commit
a77eaefb94
@ -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()));
|
||||
}
|
||||
|
@ -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]+$");
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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) + "]";
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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; $$;
|
||||
|
@ -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;
|
||||
@ -101,7 +102,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
}
|
||||
|
||||
@Test
|
||||
void globalAdmin_canViewAllAssetsByType() {
|
||||
void webspaceAgent_canViewAllAssetsByType() {
|
||||
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
@ -109,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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -0,0 +1,243 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
|
||||
import net.hostsharing.hsadminng.mapper.Array;
|
||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.SynchronizationType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_CLOUD_SERVER_BOOKING_ITEM;
|
||||
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.TestHsHostingAssetEntities.TEST_MANAGED_WEBSPACE_HOSTING_ASSET;
|
||||
import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.TEST_CONTACT;
|
||||
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@WebMvcTest(HsHostingAssetController.class)
|
||||
@Import(Mapper.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
public class HsHostingAssetControllerRestTest {
|
||||
|
||||
@Autowired
|
||||
MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
Context contextMock;
|
||||
|
||||
@Autowired
|
||||
Mapper mapper;
|
||||
|
||||
@Mock
|
||||
private EntityManager em;
|
||||
|
||||
@MockBean
|
||||
EntityManagerFactory emf;
|
||||
|
||||
@MockBean
|
||||
@SuppressWarnings("unused") // bean needs to be present for HsHostingAssetController
|
||||
private HsBookingItemRepository bookingItemRepo;
|
||||
|
||||
@MockBean
|
||||
private HsHostingAssetRepository hostingAssetRepo;
|
||||
|
||||
enum ListTestCases {
|
||||
CLOUD_SERVER(
|
||||
List.of(
|
||||
HsHostingAssetEntity.builder()
|
||||
.type(HsHostingAssetType.CLOUD_SERVER)
|
||||
.bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM)
|
||||
.identifier("vm1234")
|
||||
.caption("some fake cloud-server")
|
||||
.alarmContact(TEST_CONTACT)
|
||||
.build()),
|
||||
"""
|
||||
[
|
||||
{
|
||||
"type": "CLOUD_SERVER",
|
||||
"identifier": "vm1234",
|
||||
"caption": "some fake cloud-server",
|
||||
"alarmContact": {
|
||||
"caption": "some contact",
|
||||
"postalAddress": "address of some contact",
|
||||
"emailAddresses": {
|
||||
"main": "some-contact@example.com"
|
||||
}
|
||||
},
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
"""),
|
||||
MANAGED_SERVER(
|
||||
List.of(
|
||||
HsHostingAssetEntity.builder()
|
||||
.type(HsHostingAssetType.MANAGED_SERVER)
|
||||
.bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM)
|
||||
.identifier("vm1234")
|
||||
.caption("some fake managed-server")
|
||||
.alarmContact(TEST_CONTACT)
|
||||
.config(Map.ofEntries(
|
||||
entry("monit_max_ssd_usage", 70),
|
||||
entry("monit_max_cpu_usage", 80),
|
||||
entry("monit_max_ram_usage", 90)
|
||||
))
|
||||
.build()),
|
||||
"""
|
||||
[
|
||||
{
|
||||
"type": "MANAGED_SERVER",
|
||||
"identifier": "vm1234",
|
||||
"caption": "some fake managed-server",
|
||||
"alarmContact": {
|
||||
"caption": "some contact",
|
||||
"postalAddress": "address of some contact",
|
||||
"emailAddresses": {
|
||||
"main": "some-contact@example.com"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"monit_max_ssd_usage": 70,
|
||||
"monit_max_cpu_usage": 80,
|
||||
"monit_max_ram_usage": 90
|
||||
}
|
||||
}
|
||||
]
|
||||
"""),
|
||||
UNIX_USER(
|
||||
List.of(
|
||||
HsHostingAssetEntity.builder()
|
||||
.type(HsHostingAssetType.UNIX_USER)
|
||||
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
|
||||
.identifier("xyz00-office")
|
||||
.caption("some fake Unix-User")
|
||||
.config(Map.ofEntries(
|
||||
entry("password", "$6$salt$hashed-salted-password"),
|
||||
entry("totpKey", "0x0123456789abcdef"),
|
||||
entry("shell", "/bin/bash"),
|
||||
entry("SSD-soft-quota", 128),
|
||||
entry("SSD-hard-quota", 256),
|
||||
entry("HDD-soft-quota", 256),
|
||||
entry("HDD-hard-quota", 512)))
|
||||
.build()),
|
||||
"""
|
||||
[
|
||||
{
|
||||
"type": "UNIX_USER",
|
||||
"identifier": "xyz00-office",
|
||||
"caption": "some fake Unix-User",
|
||||
"alarmContact": null,
|
||||
"config": {
|
||||
"SSD-soft-quota": 128,
|
||||
"SSD-hard-quota": 256,
|
||||
"HDD-soft-quota": 256,
|
||||
"HDD-hard-quota": 512,
|
||||
"shell": "/bin/bash",
|
||||
"homedir": "/home/pacs/xyz00/users/office"
|
||||
}
|
||||
}
|
||||
]
|
||||
"""),
|
||||
EMAIL_ALIAS(
|
||||
List.of(
|
||||
HsHostingAssetEntity.builder()
|
||||
.type(HsHostingAssetType.EMAIL_ALIAS)
|
||||
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
|
||||
.identifier("xyz00-office")
|
||||
.caption("some fake EMail-Alias")
|
||||
.config(Map.ofEntries(
|
||||
entry("target", Array.of("xyz00", "xyz00-abc", "office@example.com"))
|
||||
))
|
||||
.build()),
|
||||
"""
|
||||
[
|
||||
{
|
||||
"type": "EMAIL_ALIAS",
|
||||
"identifier": "xyz00-office",
|
||||
"caption": "some fake EMail-Alias",
|
||||
"alarmContact": null,
|
||||
"config": {
|
||||
"target": ["xyz00","xyz00-abc","office@example.com"]
|
||||
}
|
||||
}
|
||||
]
|
||||
""");
|
||||
|
||||
final HsHostingAssetType assetType;
|
||||
final List<HsHostingAssetEntity> givenHostingAssetsOfType;
|
||||
final String expectedResponse;
|
||||
final JsonNode expectedResponseJson;
|
||||
|
||||
@SneakyThrows
|
||||
ListTestCases(
|
||||
final List<HsHostingAssetEntity> givenHostingAssetsOfType,
|
||||
final String expectedResponse) {
|
||||
this.assetType = HsHostingAssetType.valueOf(name());
|
||||
this.givenHostingAssetsOfType = givenHostingAssetsOfType;
|
||||
this.expectedResponse = expectedResponse;
|
||||
this.expectedResponseJson = new ObjectMapper().readTree(expectedResponse);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
JsonNode expectedConfig(final int n) {
|
||||
return expectedResponseJson.get(n).path("config");
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
when(emf.createEntityManager()).thenReturn(em);
|
||||
when(emf.createEntityManager(any(Map.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class), any(Map.class))).thenReturn(em);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(HsHostingAssetControllerRestTest.ListTestCases.class)
|
||||
void shouldListAssets(final HsHostingAssetControllerRestTest.ListTestCases testCase) throws Exception {
|
||||
// given
|
||||
when(hostingAssetRepo.findAllByCriteria(null, null, testCase.assetType))
|
||||
.thenReturn(testCase.givenHostingAssetsOfType);
|
||||
|
||||
// when
|
||||
final var result = mockMvc.perform(MockMvcRequestBuilders
|
||||
.get("/api/hs/hosting/assets?type="+testCase.name())
|
||||
.header("current-user", "superuser-alex@hostsharing.net")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
|
||||
// then
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("$", lenientlyEquals(testCase.expectedResponse)))
|
||||
.andReturn();
|
||||
|
||||
// and the config properties do match not just leniently but even strictly
|
||||
final var resultBody = new ObjectMapper().readTree(result.getResponse().getContentAsString());
|
||||
for (int n = 0; n < resultBody.size(); ++n) {
|
||||
assertThat(resultBody.get(n).path("config")).isEqualTo(testCase.expectedConfig(n));
|
||||
}
|
||||
}
|
||||
}
|
@ -34,7 +34,8 @@ class HsHostingAssetPropsControllerAcceptanceTest {
|
||||
"MANAGED_SERVER",
|
||||
"MANAGED_WEBSPACE",
|
||||
"CLOUD_SERVER",
|
||||
"UNIX_USER"
|
||||
"UNIX_USER",
|
||||
"EMAIL_ALIAS"
|
||||
]
|
||||
"""));
|
||||
// @formatter:on
|
||||
|
@ -0,0 +1,22 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
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;
|
||||
|
||||
public class TestHsHostingAssetEntities {
|
||||
|
||||
public static 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();
|
||||
|
||||
public static 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();
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
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.hosting.asset.HsHostingAssetType.EMAIL_ALIAS;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.TestHsHostingAssetEntities.TEST_MANAGED_SERVER_HOSTING_ASSET;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.TestHsHostingAssetEntities.TEST_MANAGED_WEBSPACE_HOSTING_ASSET;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HsEMailAliasHostingAssetValidatorUnitTest {
|
||||
|
||||
@Test
|
||||
void containsAllValidations() {
|
||||
// when
|
||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(EMAIL_ALIAS);
|
||||
|
||||
// then
|
||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||
"{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-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][a-z0-9]{2}[0-9]{2}(-[a-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");
|
||||
}
|
||||
}
|
@ -32,7 +32,8 @@ class HsHostingAssetEntityValidatorRegistryUnitTest {
|
||||
HsHostingAssetType.CLOUD_SERVER,
|
||||
HsHostingAssetType.MANAGED_SERVER,
|
||||
HsHostingAssetType.MANAGED_WEBSPACE,
|
||||
HsHostingAssetType.UNIX_USER
|
||||
HsHostingAssetType.UNIX_USER,
|
||||
HsHostingAssetType.EMAIL_ALIAS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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}"
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user