import-unix-user-and-email-aliases (#81)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/81 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
doc
src
main
java
net
hostsharing
hsadminng
hs
booking
hosting
asset
HsHostingAsset.javaHsHostingAssetController.javaHsHostingAssetEntity.javaHsHostingAssetRepository.javaHsHostingAssetType.java
validators
HostingAssetEntitySaveProcessor.javaHostingAssetEntityValidator.javaHostingAssetEntityValidatorRegistry.javaHsCloudServerHostingAssetValidator.javaHsDomainDnsSetupHostingAssetValidator.javaHsDomainHttpSetupHostingAssetValidator.javaHsDomainMboxSetupHostingAssetValidator.javaHsDomainSetupHostingAssetValidator.javaHsDomainSmtpSetupHostingAssetValidator.javaHsEMailAddressHostingAssetValidator.javaHsEMailAliasHostingAssetValidator.javaHsIPv4NumberHostingAssetValidator.javaHsIPv6NumberHostingAssetValidator.javaHsManagedServerHostingAssetValidator.javaHsManagedWebspaceHostingAssetValidator.javaHsMariaDbDatabaseHostingAssetValidator.javaHsMariaDbInstanceHostingAssetValidator.javaHsMariaDbUserHostingAssetValidator.javaHsPostgreSqlDatabaseHostingAssetValidator.javaHsPostgreSqlDbInstanceHostingAssetValidator.javaHsPostgreSqlUserHostingAssetValidator.javaHsUnixUserHostingAssetValidator.javaREADME.md
validation
mapper
stringify
resources
db
changelog
7-hs-hosting
701-hosting-asset
test
java
net
hostsharing
hsadminng
hs
hosting
asset
migration
validation
resources
migration
hosting
@@ -32,6 +32,7 @@ import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.Version;
|
||||
@@ -124,6 +125,14 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject<HsBookingI
|
||||
@Transient
|
||||
private PatchableMapWrapper<Object> resourcesWrapper;
|
||||
|
||||
@Transient
|
||||
private boolean isLoaded;
|
||||
|
||||
@PostLoad
|
||||
public void markAsLoaded() {
|
||||
this.isLoaded = true;
|
||||
}
|
||||
|
||||
public PatchableMapWrapper<Object> getResources() {
|
||||
return PatchableMapWrapper.of(resourcesWrapper, (newWrapper) -> {resourcesWrapper = newWrapper; }, resources );
|
||||
}
|
||||
|
@@ -38,8 +38,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
|
||||
);
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> unixUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> unixUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
|
||||
.map(ha -> ha.getSubHostingAssets().stream()
|
||||
.filter(subAsset -> subAsset.getType() == UNIX_USER)
|
||||
@@ -53,8 +53,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databaseUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> databaseUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
final var dbUserCount = ofNullable(entity.getRelatedHostingAsset())
|
||||
.map(ha -> ha.getSubHostingAssets().stream()
|
||||
.filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER )
|
||||
@@ -68,8 +68,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databases() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> databases() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
|
||||
.map(ha -> ha.getSubHostingAssets().stream()
|
||||
.filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER )
|
||||
@@ -85,8 +85,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> eMailAddresses() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> eMailAddresses() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
|
||||
.map(ha -> ha.getSubHostingAssets().stream()
|
||||
.filter(bi -> bi.getType() == DOMAIN_MBOX_SETUP)
|
||||
|
@@ -0,0 +1,72 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
|
||||
public interface HsHostingAsset extends Stringifyable, RbacObject<HsHostingAsset>, PropertiesProvider {
|
||||
|
||||
Stringify<HsHostingAsset> stringify = stringify(HsHostingAsset.class)
|
||||
.withProp(HsHostingAsset::getType)
|
||||
.withProp(HsHostingAsset::getIdentifier)
|
||||
.withProp(HsHostingAsset::getCaption)
|
||||
.withProp(HsHostingAsset::getParentAsset)
|
||||
.withProp(HsHostingAsset::getAssignedToAsset)
|
||||
.withProp(HsHostingAsset::getBookingItem)
|
||||
.withProp(HsHostingAsset::getConfig)
|
||||
.quotedValues(false);
|
||||
|
||||
|
||||
void setUuid(UUID uuid);
|
||||
HsHostingAssetType getType();
|
||||
HsHostingAsset getParentAsset();
|
||||
void setIdentifier(String s);
|
||||
String getIdentifier();
|
||||
HsBookingItemEntity getBookingItem();
|
||||
HsHostingAsset getAssignedToAsset();
|
||||
HsOfficeContactEntity getAlarmContact();
|
||||
List<? extends HsHostingAsset> getSubHostingAssets();
|
||||
String getCaption();
|
||||
Map<String, Object> getConfig();
|
||||
|
||||
default HsBookingProjectEntity getRelatedProject() {
|
||||
return Optional.ofNullable(getBookingItem())
|
||||
.map(HsBookingItemEntity::getRelatedProject)
|
||||
.orElseGet(() -> Optional.ofNullable(getParentAsset())
|
||||
.map(HsHostingAsset::getRelatedProject)
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
default Object getContextValue(final String propName) {
|
||||
final var v = directProps().get(propName);
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
|
||||
if (getBookingItem() != null) {
|
||||
return getBookingItem().getResources().get(propName);
|
||||
}
|
||||
if (getParentAsset() != null && getParentAsset().getBookingItem() != null) {
|
||||
return getParentAsset().getBookingItem().getResources().get(propName);
|
||||
}
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
default String toShortString() {
|
||||
return getType() + ":" + getIdentifier();
|
||||
}
|
||||
}
|
@@ -72,7 +72,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
final var entity = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var mapped = new HostingAssetEntitySaveProcessor(entity)
|
||||
final var mapped = new HostingAssetEntitySaveProcessor(em, entity)
|
||||
.preprocessEntity()
|
||||
.validateEntity()
|
||||
.prepareForSave()
|
||||
@@ -133,7 +133,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
new HsHostingAssetEntityPatcher(em, entity).apply(body);
|
||||
|
||||
final var mapped = new HostingAssetEntitySaveProcessor(entity)
|
||||
final var mapped = new HostingAssetEntitySaveProcessor(em, entity)
|
||||
.preprocessEntity()
|
||||
.validateEntity()
|
||||
.prepareForSave()
|
||||
@@ -162,5 +162,5 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
@SuppressWarnings("unchecked")
|
||||
final BiConsumer<HsHostingAssetEntity, HsHostingAssetResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource)
|
||||
-> resource.setConfig(HostingAssetEntityValidatorRegistry.forType(entity.getType())
|
||||
.revampProperties(entity, (Map<String, Object>) resource.getConfig()));
|
||||
.revampProperties(em, entity, (Map<String, Object>) resource.getConfig()));
|
||||
}
|
||||
|
@@ -8,15 +8,10 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||
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;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
@@ -39,10 +34,8 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inCaseOf;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
|
||||
@@ -70,17 +63,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject<HsHostingAssetEntity>, PropertiesProvider {
|
||||
|
||||
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
||||
.withProp(HsHostingAssetEntity::getType)
|
||||
.withProp(HsHostingAssetEntity::getIdentifier)
|
||||
.withProp(HsHostingAssetEntity::getCaption)
|
||||
.withProp(HsHostingAssetEntity::getParentAsset)
|
||||
.withProp(HsHostingAssetEntity::getAssignedToAsset)
|
||||
.withProp(HsHostingAssetEntity::getBookingItem)
|
||||
.withProp(HsHostingAssetEntity::getConfig)
|
||||
.quotedValues(false);
|
||||
public class HsHostingAssetEntity implements HsHostingAsset {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@@ -136,14 +119,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject<HsHosting
|
||||
this.isLoaded = true;
|
||||
}
|
||||
|
||||
public HsBookingProjectEntity getRelatedProject() {
|
||||
return Optional.ofNullable(bookingItem)
|
||||
.map(HsBookingItemEntity::getRelatedProject)
|
||||
.orElseGet(() -> Optional.ofNullable(parentAsset)
|
||||
.map(HsHostingAssetEntity::getRelatedProject)
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
public PatchableMapWrapper<Object> getConfig() {
|
||||
return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config);
|
||||
}
|
||||
@@ -157,30 +132,9 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject<HsHosting
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return type + ":" + identifier;
|
||||
return stringify.using(HsHostingAssetEntity.class).apply(this);
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
|
@@ -39,7 +39,7 @@ public interface HsHostingAssetRepository extends Repository<HsHostingAssetEntit
|
||||
return findAllByCriteriaImpl(projectUuid, parentAssetUuid, HsHostingAssetType.asString(type));
|
||||
}
|
||||
|
||||
HsHostingAssetEntity save(HsHostingAssetEntity current);
|
||||
HsHostingAssetEntity save(HsHostingAsset current);
|
||||
|
||||
int deleteByUuid(final UUID uuid);
|
||||
|
||||
|
@@ -384,29 +384,29 @@ class EntityTypeRelation<E, T extends Node> {
|
||||
" *==> ");
|
||||
}
|
||||
|
||||
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> optionalParent(final HsHostingAssetType hostingAssetType) {
|
||||
static EntityTypeRelation<HsHostingAsset, HsHostingAssetType> optionalParent(final HsHostingAssetType hostingAssetType) {
|
||||
return new EntityTypeRelation<>(
|
||||
OPTIONAL,
|
||||
PARENT_ASSET,
|
||||
HsHostingAssetEntity::getParentAsset,
|
||||
HsHostingAsset::getParentAsset,
|
||||
hostingAssetType,
|
||||
" o..> ");
|
||||
}
|
||||
|
||||
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> requiredParent(final HsHostingAssetType hostingAssetType) {
|
||||
static EntityTypeRelation<HsHostingAsset, HsHostingAssetType> requiredParent(final HsHostingAssetType hostingAssetType) {
|
||||
return new EntityTypeRelation<>(
|
||||
REQUIRED,
|
||||
PARENT_ASSET,
|
||||
HsHostingAssetEntity::getParentAsset,
|
||||
HsHostingAsset::getParentAsset,
|
||||
hostingAssetType,
|
||||
" *==> ");
|
||||
}
|
||||
|
||||
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> assignedTo(final HsHostingAssetType hostingAssetType) {
|
||||
static EntityTypeRelation<HsHostingAsset, HsHostingAssetType> assignedTo(final HsHostingAssetType hostingAssetType) {
|
||||
return new EntityTypeRelation<>(
|
||||
REQUIRED,
|
||||
ASSIGNED_TO_ASSET,
|
||||
HsHostingAssetEntity::getAssignedToAsset,
|
||||
HsHostingAsset::getAssignedToAsset,
|
||||
hostingAssetType,
|
||||
" o--> ");
|
||||
}
|
||||
@@ -416,11 +416,11 @@ class EntityTypeRelation<E, T extends Node> {
|
||||
return this;
|
||||
}
|
||||
|
||||
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> optionallyAssignedTo(final HsHostingAssetType hostingAssetType) {
|
||||
static EntityTypeRelation<HsHostingAsset, HsHostingAssetType> optionallyAssignedTo(final HsHostingAssetType hostingAssetType) {
|
||||
return new EntityTypeRelation<>(
|
||||
OPTIONAL,
|
||||
ASSIGNED_TO_ASSET,
|
||||
HsHostingAssetEntity::getAssignedToAsset,
|
||||
HsHostingAsset::getAssignedToAsset,
|
||||
hostingAssetType,
|
||||
" o..> ");
|
||||
}
|
||||
|
@@ -1,24 +1,27 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Wraps the steps of the pararation, validation, mapping and revamp around saving of a HsHostingAssetEntity into a readable API.
|
||||
* Wraps the steps of the pararation, validation, mapping and revamp around saving of a HsHostingAsset into a readable API.
|
||||
*/
|
||||
public class HostingAssetEntitySaveProcessor {
|
||||
|
||||
private final HsEntityValidator<HsHostingAssetEntity> validator;
|
||||
private final HsEntityValidator<HsHostingAsset> validator;
|
||||
private String expectedStep = "preprocessEntity";
|
||||
private HsHostingAssetEntity entity;
|
||||
private final EntityManager em;
|
||||
private HsHostingAsset entity;
|
||||
private HsHostingAssetResource resource;
|
||||
|
||||
public HostingAssetEntitySaveProcessor(final HsHostingAssetEntity entity) {
|
||||
public HostingAssetEntitySaveProcessor(final EntityManager em, final HsHostingAsset entity) {
|
||||
this.em = em;
|
||||
this.entity = entity;
|
||||
this.validator = HostingAssetEntityValidatorRegistry.forType(entity.getType());
|
||||
}
|
||||
@@ -37,15 +40,26 @@ public class HostingAssetEntitySaveProcessor {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// validates the entity itself including its properties, but ignoring some error messages for import of legacy data
|
||||
public HostingAssetEntitySaveProcessor validateEntityIgnoring(final String ignoreRegExp) {
|
||||
step("validateEntity", "prepareForSave");
|
||||
MultiValidationException.throwIfNotEmpty(
|
||||
validator.validateEntity(entity).stream()
|
||||
.filter(errorMsg -> !errorMsg.matches(ignoreRegExp))
|
||||
.toList()
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// hashing passwords etc.
|
||||
@SuppressWarnings("unchecked")
|
||||
public HostingAssetEntitySaveProcessor prepareForSave() {
|
||||
step("prepareForSave", "saveUsing");
|
||||
validator.prepareProperties(entity);
|
||||
validator.prepareProperties(em, entity);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HostingAssetEntitySaveProcessor saveUsing(final Function<HsHostingAssetEntity, HsHostingAssetEntity> saveFunction) {
|
||||
public HostingAssetEntitySaveProcessor saveUsing(final Function<HsHostingAsset, HsHostingAsset> saveFunction) {
|
||||
step("saveUsing", "validateContext");
|
||||
entity = saveFunction.apply(entity);
|
||||
return this;
|
||||
@@ -60,7 +74,7 @@ public class HostingAssetEntitySaveProcessor {
|
||||
|
||||
/// maps entity to JSON resource representation
|
||||
public HostingAssetEntitySaveProcessor mapUsing(
|
||||
final Function<HsHostingAssetEntity, HsHostingAssetResource> mapFunction) {
|
||||
final Function<HsHostingAsset, HsHostingAssetResource> mapFunction) {
|
||||
step("mapUsing", "revampProperties");
|
||||
resource = mapFunction.apply(entity);
|
||||
return this;
|
||||
@@ -70,7 +84,7 @@ public class HostingAssetEntitySaveProcessor {
|
||||
@SuppressWarnings("unchecked")
|
||||
public HsHostingAssetResource revampProperties() {
|
||||
step("revampProperties", null);
|
||||
final var revampedProps = validator.revampProperties(entity, (Map<String, Object>) resource.getConfig());
|
||||
final var revampedProps = validator.revampProperties(em, entity, (Map<String, Object>) resource.getConfig());
|
||||
resource.setConfig(revampedProps);
|
||||
return resource;
|
||||
}
|
||||
|
50
src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidator.java
50
src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntityValidator.java
@@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
@@ -23,13 +23,13 @@ import static java.util.Arrays.stream;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity> {
|
||||
public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHostingAsset> {
|
||||
|
||||
static final ValidatableProperty<?, ?>[] NO_EXTRA_PROPERTIES = new ValidatableProperty<?, ?>[0];
|
||||
|
||||
private final ReferenceValidator<HsBookingItemEntity, HsBookingItemType> bookingItemReferenceValidation;
|
||||
private final ReferenceValidator<HsHostingAssetEntity, HsHostingAssetType> parentAssetReferenceValidation;
|
||||
private final ReferenceValidator<HsHostingAssetEntity, HsHostingAssetType> assignedToAssetReferenceValidation;
|
||||
private final ReferenceValidator<HsHostingAsset, HsHostingAssetType> parentAssetReferenceValidation;
|
||||
private final ReferenceValidator<HsHostingAsset, HsHostingAssetType> assignedToAssetReferenceValidation;
|
||||
private final HostingAssetEntityValidator.AlarmContact alarmContactValidation;
|
||||
|
||||
HostingAssetEntityValidator(
|
||||
@@ -40,23 +40,23 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
this.bookingItemReferenceValidation = new ReferenceValidator<>(
|
||||
assetType.bookingItemPolicy(),
|
||||
assetType.bookingItemTypes(),
|
||||
HsHostingAssetEntity::getBookingItem,
|
||||
HsHostingAsset::getBookingItem,
|
||||
HsBookingItemEntity::getType);
|
||||
this.parentAssetReferenceValidation = new ReferenceValidator<>(
|
||||
assetType.parentAssetPolicy(),
|
||||
assetType.parentAssetTypes(),
|
||||
HsHostingAssetEntity::getParentAsset,
|
||||
HsHostingAssetEntity::getType);
|
||||
HsHostingAsset::getParentAsset,
|
||||
HsHostingAsset::getType);
|
||||
this.assignedToAssetReferenceValidation = new ReferenceValidator<>(
|
||||
assetType.assignedToAssetPolicy(),
|
||||
assetType.assignedToAssetTypes(),
|
||||
HsHostingAssetEntity::getAssignedToAsset,
|
||||
HsHostingAssetEntity::getType);
|
||||
HsHostingAsset::getAssignedToAsset,
|
||||
HsHostingAsset::getType);
|
||||
this.alarmContactValidation = alarmContactValidation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validateEntity(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
||||
return sequentiallyValidate(
|
||||
() -> validateEntityReferencesAndProperties(assetEntity),
|
||||
() -> validateIdentifierPattern(assetEntity)
|
||||
@@ -64,7 +64,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validateContext(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateContext(final HsHostingAsset assetEntity) {
|
||||
return sequentiallyValidate(
|
||||
() -> optionallyValidate(assetEntity.getBookingItem()),
|
||||
() -> optionallyValidate(assetEntity.getParentAsset()),
|
||||
@@ -72,7 +72,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> validateEntityReferencesAndProperties(final HsHostingAssetEntity assetEntity) {
|
||||
private List<String> validateEntityReferencesAndProperties(final HsHostingAsset assetEntity) {
|
||||
return Stream.of(
|
||||
validateReferencedEntity(assetEntity, "bookingItem", bookingItemReferenceValidation::validate),
|
||||
validateReferencedEntity(assetEntity, "parentAsset", parentAssetReferenceValidation::validate),
|
||||
@@ -86,17 +86,17 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
}
|
||||
|
||||
private List<String> validateReferencedEntity(
|
||||
final HsHostingAssetEntity assetEntity,
|
||||
final HsHostingAsset assetEntity,
|
||||
final String referenceFieldName,
|
||||
final BiFunction<HsHostingAssetEntity, String, List<String>> validator) {
|
||||
final BiFunction<HsHostingAsset, String, List<String>> validator) {
|
||||
return enrich(prefix(assetEntity.toShortString()), validator.apply(assetEntity, referenceFieldName));
|
||||
}
|
||||
|
||||
private List<String> validateProperties(final HsHostingAssetEntity assetEntity) {
|
||||
private List<String> validateProperties(final HsHostingAsset assetEntity) {
|
||||
return enrich(prefix(assetEntity.toShortString(), "config"), super.validateProperties(assetEntity));
|
||||
}
|
||||
|
||||
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
|
||||
private static List<String> optionallyValidate(final HsHostingAsset assetEntity) {
|
||||
return assetEntity != null
|
||||
? enrich(
|
||||
prefix(assetEntity.toShortString(), "parentAsset"),
|
||||
@@ -112,7 +112,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
: emptyList();
|
||||
}
|
||||
|
||||
protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) {
|
||||
protected List<String> validateAgainstSubEntities(final HsHostingAsset assetEntity) {
|
||||
return enrich(
|
||||
prefix(assetEntity.toShortString(), "config"),
|
||||
stream(propertyValidators)
|
||||
@@ -124,7 +124,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
|
||||
// TODO.test: check, if there are any hosting assets which need this validation at all
|
||||
private String validateMaxTotalValue(
|
||||
final HsHostingAssetEntity hostingAsset,
|
||||
final HsHostingAsset hostingAsset,
|
||||
final ValidatableProperty<?, ?> propDef) {
|
||||
final var propName = propDef.propertyName();
|
||||
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
|
||||
@@ -140,7 +140,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
: null;
|
||||
}
|
||||
|
||||
private List<String> validateIdentifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
private List<String> validateIdentifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var expectedIdentifierPattern = identifierPattern(assetEntity);
|
||||
if (assetEntity.getIdentifier() == null ||
|
||||
!expectedIdentifierPattern.matcher(assetEntity.getIdentifier()).matches()) {
|
||||
@@ -151,19 +151,19 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
protected abstract Pattern identifierPattern(HsHostingAssetEntity assetEntity);
|
||||
protected abstract Pattern identifierPattern(HsHostingAsset assetEntity);
|
||||
|
||||
static class ReferenceValidator<S, T> {
|
||||
|
||||
private final HsHostingAssetType.RelationPolicy policy;
|
||||
private final Set<T> referencedEntityTypes;
|
||||
private final Function<HsHostingAssetEntity, S> referencedEntityGetter;
|
||||
private final Function<HsHostingAsset, S> referencedEntityGetter;
|
||||
private final Function<S, T> referencedEntityTypeGetter;
|
||||
|
||||
public ReferenceValidator(
|
||||
final HsHostingAssetType.RelationPolicy policy,
|
||||
final Set<T> referencedEntityTypes,
|
||||
final Function<HsHostingAssetEntity, S> referencedEntityGetter,
|
||||
final Function<HsHostingAsset, S> referencedEntityGetter,
|
||||
final Function<S, T> referencedEntityTypeGetter) {
|
||||
this.policy = policy;
|
||||
this.referencedEntityTypes = referencedEntityTypes;
|
||||
@@ -173,14 +173,14 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
|
||||
public ReferenceValidator(
|
||||
final HsHostingAssetType.RelationPolicy policy,
|
||||
final Function<HsHostingAssetEntity, S> referencedEntityGetter) {
|
||||
final Function<HsHostingAsset, S> referencedEntityGetter) {
|
||||
this.policy = policy;
|
||||
this.referencedEntityTypes = Set.of();
|
||||
this.referencedEntityGetter = referencedEntityGetter;
|
||||
this.referencedEntityTypeGetter = e -> null;
|
||||
}
|
||||
|
||||
List<String> validate(final HsHostingAssetEntity assetEntity, final String referenceFieldName) {
|
||||
List<String> validate(final HsHostingAsset assetEntity, final String referenceFieldName) {
|
||||
|
||||
final var actualEntity = referencedEntityGetter.apply(assetEntity);
|
||||
final var actualEntityType = actualEntity != null ? referencedEntityTypeGetter.apply(actualEntity) : null;
|
||||
@@ -216,7 +216,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
static class AlarmContact extends ReferenceValidator<HsOfficeContactEntity, Enum<?>> {
|
||||
|
||||
AlarmContact(final HsHostingAssetType.RelationPolicy policy) {
|
||||
super(policy, HsHostingAssetEntity::getAlarmContact);
|
||||
super(policy, HsHostingAsset::getAlarmContact);
|
||||
}
|
||||
|
||||
// hostmaster alert address is implicitly added where neccessary
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
@@ -12,7 +13,7 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.*;
|
||||
|
||||
public class HostingAssetEntityValidatorRegistry {
|
||||
|
||||
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity>> validators = new HashMap<>();
|
||||
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAsset>> validators = new HashMap<>();
|
||||
static {
|
||||
// HOWTO: add (register) new HsHostingAssetType-specific validators
|
||||
register(CLOUD_SERVER, new HsCloudServerHostingAssetValidator());
|
||||
@@ -36,14 +37,14 @@ public class HostingAssetEntityValidatorRegistry {
|
||||
register(IPV6_NUMBER, new HsIPv6NumberHostingAssetValidator());
|
||||
}
|
||||
|
||||
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity> validator) {
|
||||
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAsset> validator) {
|
||||
stream(validator.propertyValidators).forEach( entry -> {
|
||||
entry.verifyConsistency(Map.entry(type, validator));
|
||||
});
|
||||
validators.put(type, validator);
|
||||
}
|
||||
|
||||
public static HsEntityValidator<HsHostingAssetEntity> forType(final Enum<HsHostingAssetType> type) {
|
||||
public static HsEntityValidator<HsHostingAsset> forType(final Enum<HsHostingAssetType> type) {
|
||||
if ( validators.containsKey(type)) {
|
||||
return validators.get(type);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -16,7 +16,7 @@ class HsCloudServerHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^vm[0-9][0-9][0-9][0-9]$");
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.system.SystemProcess;
|
||||
|
||||
import java.util.List;
|
||||
@@ -59,12 +59,12 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
|
||||
@@ -73,7 +73,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public List<String> validateContext(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateContext(final HsHostingAsset assetEntity) {
|
||||
final var result = super.validateContext(assetEntity);
|
||||
|
||||
// TODO.spec: define which checks should get raised to error level
|
||||
@@ -87,7 +87,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
return result;
|
||||
}
|
||||
|
||||
String toZonefileString(final HsHostingAssetEntity assetEntity) {
|
||||
String toZonefileString(final HsHostingAsset assetEntity) {
|
||||
// TODO.spec: we need to expand the templates (auto-...) in the same way as in Saltstack
|
||||
return """
|
||||
$ORIGIN {domain}.
|
||||
@@ -104,7 +104,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
.replace("{userRRs}", getPropertyValues(assetEntity, "user-RR") );
|
||||
}
|
||||
|
||||
private String fqdn(final HsHostingAssetEntity assetEntity) {
|
||||
private String fqdn(final HsHostingAsset assetEntity) {
|
||||
return assetEntity.getIdentifier().substring(0, assetEntity.getIdentifier().length()-IDENTIFIER_SUFFIX.length());
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -42,12 +42,12 @@ class HsDomainHttpSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -20,12 +20,12 @@ class HsDomainMboxSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -22,7 +22,7 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validateEntity(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
||||
// TODO.impl: for newly created entities, check the permission of setting up a domain
|
||||
//
|
||||
// reject, if the domain is any of these:
|
||||
@@ -51,7 +51,7 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return identifierPattern;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -20,12 +20,12 @@ class HsDomainSmtpSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@@ -11,7 +11,7 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
|
||||
|
||||
class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
|
||||
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 UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$"; // also accepts legacy pac-names
|
||||
private static final String EMAIL_ADDRESS_LOCAL_PART_REGEX = "[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+"; // RFC 5322
|
||||
private static final String EMAIL_ADDRESS_DOMAIN_PART_REGEX = "[a-zA-Z0-9.-]+";
|
||||
private static final String EMAIL_ADDRESS_FULL_REGEX = "^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "@" + EMAIL_ADDRESS_DOMAIN_PART_REGEX + "$";
|
||||
@@ -29,7 +29,7 @@ class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
@@ -38,11 +38,11 @@ class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^"+ Pattern.quote(combineIdentifier(assetEntity)) + "$");
|
||||
}
|
||||
|
||||
private static String combineIdentifier(final HsHostingAssetEntity emailAddressAssetEntity) {
|
||||
private static String combineIdentifier(final HsHostingAsset emailAddressAssetEntity) {
|
||||
return emailAddressAssetEntity.getDirectValue("local-part", String.class) +
|
||||
ofNullable(emailAddressAssetEntity.getDirectValue("sub-domain", String.class)).map(s -> "." + s).orElse("") +
|
||||
"@" +
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@@ -10,8 +10,11 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
|
||||
|
||||
class HsEMailAliasHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
|
||||
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 UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][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
|
||||
private static final String INCLUDE_REGEX = "^:include:/.*$";
|
||||
private static final String PIPE_REGEX = "^\\|.*$";
|
||||
private static final String DEV_NULL_REGEX = "^/dev/null$";
|
||||
public static final int EMAIL_ADDRESS_MAX_LENGTH = 320; // according to RFC 5321 and RFC 5322
|
||||
|
||||
HsEMailAliasHostingAssetValidator() {
|
||||
@@ -19,13 +22,13 @@ class HsEMailAliasHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
AlarmContact.isOptional(),
|
||||
|
||||
arrayOf(
|
||||
stringProperty("target").maxLength(EMAIL_ADDRESS_MAX_LENGTH).matchesRegEx(UNIX_USER_REGEX, EMAIL_ADDRESS_REGEX)
|
||||
stringProperty("target").maxLength(EMAIL_ADDRESS_MAX_LENGTH).matchesRegEx(UNIX_USER_REGEX, EMAIL_ADDRESS_REGEX, INCLUDE_REGEX, PIPE_REGEX, DEV_NULL_REGEX)
|
||||
).required().minLength(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9]+$");
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9][a-z0-9\\._-]*$");
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -20,7 +20,7 @@ class HsIPv4NumberHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return IPV4_REGEX;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -24,7 +24,7 @@ class HsIPv6NumberHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validateEntity(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
||||
final var violations = super.validateEntity(assetEntity);
|
||||
|
||||
if (!isValidIPv6Address(assetEntity.getIdentifier())) {
|
||||
@@ -35,7 +35,7 @@ class HsIPv6NumberHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return IPV6_REGEX;
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -54,7 +54,7 @@ class HsManagedServerHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^vm[0-9][0-9][0-9][0-9]$");
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -11,11 +11,11 @@ class HsManagedWebspaceHostingAssetValidator extends HostingAssetEntityValidator
|
||||
super(
|
||||
MANAGED_WEBSPACE,
|
||||
AlarmContact.isOptional(),
|
||||
NO_EXTRA_PROPERTIES);
|
||||
NO_EXTRA_PROPERTIES); // TODO.impl: groupid missing, should be equal to main user
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var prefixPattern =
|
||||
!assetEntity.isLoaded()
|
||||
? assetEntity.getRelatedProject().getDebitor().getDefaultPrefix()
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -18,7 +18,7 @@ class HsMariaDbDatabaseHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var webspaceIdentifier = assetEntity.getParentAsset().getParentAsset().getIdentifier();
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$");
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -19,7 +19,7 @@ class HsMariaDbInstanceHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile(
|
||||
"^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier()
|
||||
+ DEFAULT_INSTANCE_IDENTIFIER_SUFFIX)
|
||||
@@ -27,7 +27,7 @@ class HsMariaDbInstanceHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -26,7 +26,7 @@ class HsMariaDbUserHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$");
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -21,7 +21,7 @@ class HsPostgreSqlDatabaseHostingAssetValidator extends HostingAssetEntityValida
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var webspaceIdentifier = assetEntity.getParentAsset().getParentAsset().getIdentifier();
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$");
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -21,7 +21,7 @@ class HsPostgreSqlDbInstanceHostingAssetValidator extends HostingAssetEntityVali
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile(
|
||||
"^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier()
|
||||
+ DEFAULT_INSTANCE_IDENTIFIER_SUFFIX)
|
||||
@@ -29,7 +29,7 @@ class HsPostgreSqlDbInstanceHostingAssetValidator extends HostingAssetEntityVali
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -26,7 +26,7 @@ class HsPostgreSqlUserHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$");
|
||||
}
|
||||
|
@@ -1,13 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
|
||||
@@ -21,29 +22,39 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
HsHostingAssetType.UNIX_USER,
|
||||
AlarmContact.isOptional(),
|
||||
|
||||
integerProperty("SSD hard quota").unit("GB").maxFrom("SSD").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").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")
|
||||
booleanProperty("locked").readOnly(),
|
||||
integerProperty("userid").readOnly().initializedBy(HsUnixUserHostingAssetValidator::computeUserId),
|
||||
|
||||
integerProperty("SSD hard quota").unit("MB").maxFrom("SSD").withFactor(1024).optional(),
|
||||
integerProperty("SSD soft quota").unit("MB").maxFrom("SSD hard quota").optional(),
|
||||
integerProperty("HDD hard quota").unit("MB").maxFrom("HDD").withFactor(1024).optional(),
|
||||
integerProperty("HDD soft quota").unit("MB").maxFrom("HDD hard quota").optional(),
|
||||
stringProperty("shell")
|
||||
// TODO.spec: do we want to change them all to /usr/bin/, also in import?
|
||||
.provided("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd")
|
||||
.withDefault("/bin/false"),
|
||||
stringProperty("homedir").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeHomedir),
|
||||
stringProperty("homedir").readOnly().renderedBy(HsUnixUserHostingAssetValidator::computeHomedir),
|
||||
stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).undisclosed().writeOnly().optional(),
|
||||
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(HashGenerator.Algorithm.LINUX_SHA512).writeOnly());
|
||||
// TODO.spec: public SSH keys?
|
||||
// TODO.spec: public SSH keys? (only if hsadmin-ng is only accessible with 2FA)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9]+$");
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9\\._-]+$");
|
||||
}
|
||||
|
||||
private static String computeHomedir(final PropertiesProvider propertiesProvider) {
|
||||
final var entity = (HsHostingAssetEntity) propertiesProvider;
|
||||
private static String computeHomedir(final EntityManager em, final PropertiesProvider propertiesProvider) {
|
||||
final var entity = (HsHostingAsset) propertiesProvider;
|
||||
final var webspaceName = entity.getParentAsset().getIdentifier();
|
||||
return "/home/pacs/" + webspaceName
|
||||
+ "/users/" + entity.getIdentifier().substring(webspaceName.length()+DASH_LENGTH);
|
||||
}
|
||||
|
||||
private static Integer computeUserId(final EntityManager em, final PropertiesProvider propertiesProvider) {
|
||||
final Object result = em.createNativeQuery("SELECT nextval('hs_hosting_asset_unixuser_system_id_seq')", Integer.class)
|
||||
.getSingleResult();
|
||||
return (Integer) result;
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
### HsHostingAssetEntity-Validation
|
||||
### HsHostingAsset-Validation
|
||||
|
||||
There is just a single `HsHostingAssetEntity` class for all types of hosting assets like Managed-Server, Managed-Webspace, Unix-Users, Databases etc. These are distinguished by `HsHostingAssetType HsHostingAssetEntity.type`.
|
||||
There is just a single `HsHostingAsset` interface and `HsHostingAssetEntity` entity for all types of hosting assets like Managed-Server, Managed-Webspace, Unix-Users, Databases etc. These are distinguished by `HsHostingAssetType HsHostingAsset.type`.
|
||||
|
||||
For each of these types, a distinct validator has to be
|
||||
implemented as a subclass of `HsHostingAssetEntityValidator` which needs to be registered (see `HsHostingAssetEntityValidatorRegistry`) for the relevant type(s).
|
||||
implemented as a subclass of `HsHostingAssetValidator` which needs to be registered (see `HsHostingAssetValidatorRegistry`) for the relevant type(s).
|
||||
|
||||
### Kinds of Validations
|
||||
|
||||
@@ -21,7 +21,7 @@ References in this context are:
|
||||
- the Assigned-To-Hosting-Asset and
|
||||
- the Contact.
|
||||
|
||||
The first parameters of the `HsHostingAssetEntityValidator` superclass take rule descriptors for these references. These are all Subclasses fo
|
||||
The first parameters of the `HsHostingAssetValidator` superclass take rule descriptors for these references. These are all Subclasses fo
|
||||
|
||||
### Validation Order
|
||||
|
||||
@@ -37,4 +37,4 @@ In general, the validation es executed in this order:
|
||||
2. the limits of the parent entity (parent asset + booking item)
|
||||
3. limits against the own own-sub-entities
|
||||
|
||||
This implementation can be found in `HsHostingAssetEntityValidator.validate`.
|
||||
This implementation can be found in `HsHostingAssetValidator.validate`.
|
||||
|
@@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -13,6 +14,9 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.hostsharing.hsadminng.hs.validation.ValidatableProperty.ComputeMode.IN_INIT;
|
||||
import static net.hostsharing.hsadminng.hs.validation.ValidatableProperty.ComputeMode.IN_PREP;
|
||||
import static net.hostsharing.hsadminng.hs.validation.ValidatableProperty.ComputeMode.IN_REVAMP;
|
||||
|
||||
// TODO.refa: rename to HsEntityProcessor, also subclasses
|
||||
public abstract class HsEntityValidator<E extends PropertiesProvider> {
|
||||
@@ -106,21 +110,21 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
|
||||
throw new IllegalArgumentException("Integer value (or null) expected, but got " + value);
|
||||
}
|
||||
|
||||
public void prepareProperties(final E entity) {
|
||||
public void prepareProperties(final EntityManager em, final E entity) {
|
||||
stream(propertyValidators).forEach(p -> {
|
||||
if ( p.isWriteOnly() && p.isComputed()) {
|
||||
entity.directProps().put(p.propertyName, p.compute(entity));
|
||||
if (p.isComputed(IN_PREP) || p.isComputed(IN_INIT) && !entity.isLoaded() ) {
|
||||
entity.directProps().put(p.propertyName, p.compute(em, entity));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Map<String, Object> revampProperties(final E entity, final Map<String, Object> config) {
|
||||
public Map<String, Object> revampProperties(final EntityManager em, final E entity, final Map<String, Object> config) {
|
||||
final var copy = new HashMap<>(config);
|
||||
stream(propertyValidators).forEach(p -> {
|
||||
if (p.isWriteOnly()) {
|
||||
copy.remove(p.propertyName);
|
||||
} else if (p.isReadOnly() && p.isComputed()) {
|
||||
copy.put(p.propertyName, p.compute(entity));
|
||||
} else if (p.isComputed(IN_REVAMP)) {
|
||||
copy.put(p.propertyName, p.compute(em, entity));
|
||||
}
|
||||
});
|
||||
return copy;
|
||||
|
@@ -7,7 +7,7 @@ import org.apache.commons.lang3.Validate;
|
||||
import java.util.List;
|
||||
|
||||
@Setter
|
||||
public class IntegerProperty extends ValidatableProperty<IntegerProperty, Integer> {
|
||||
public class IntegerProperty<P extends IntegerProperty<P>> extends ValidatableProperty<P, Integer> {
|
||||
|
||||
private final static String[] KEY_ORDER = Array.join(
|
||||
ValidatableProperty.KEY_ORDER_HEAD,
|
||||
@@ -19,10 +19,11 @@ public class IntegerProperty extends ValidatableProperty<IntegerProperty, Intege
|
||||
private String minFrom;
|
||||
private Integer max;
|
||||
private String maxFrom;
|
||||
private Integer factor;
|
||||
private Integer step;
|
||||
|
||||
public static IntegerProperty integerProperty(final String propertyName) {
|
||||
return new IntegerProperty(propertyName);
|
||||
public static IntegerProperty<?> integerProperty(final String propertyName) {
|
||||
return new IntegerProperty<>(propertyName);
|
||||
}
|
||||
|
||||
private IntegerProperty(final String propertyName) {
|
||||
@@ -35,14 +36,19 @@ public class IntegerProperty extends ValidatableProperty<IntegerProperty, Intege
|
||||
Validate.isTrue(max == null || maxFrom == null, "max and maxFrom are exclusive, but both are given");
|
||||
}
|
||||
|
||||
public IntegerProperty minFrom(final String propertyName) {
|
||||
public P minFrom(final String propertyName) {
|
||||
minFrom = propertyName;
|
||||
return this;
|
||||
return self();
|
||||
}
|
||||
|
||||
public IntegerProperty maxFrom(final String propertyName) {
|
||||
public P maxFrom(final String propertyName) {
|
||||
maxFrom = propertyName;
|
||||
return this;
|
||||
return self();
|
||||
}
|
||||
|
||||
public P withFactor(final int factor) {
|
||||
this.factor = factor;
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,10 +68,10 @@ public class IntegerProperty extends ValidatableProperty<IntegerProperty, Intege
|
||||
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));
|
||||
validateMin(result, propertyName, propValue, propProvider.getContextValue(minFrom, Integer.class) * ((factor != null) ? factor : 1));
|
||||
}
|
||||
if (maxFrom != null) {
|
||||
validateMax(result, propertyName, propValue, propProvider.getContextValue(maxFrom, Integer.class, 0));
|
||||
validateMax(result, propertyName, propValue, propProvider.getContextValue(maxFrom, Integer.class, 0) * ((factor != null) ? factor : 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||
import net.hostsharing.hsadminng.hash.HashGenerator.Algorithm;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
@@ -13,7 +13,10 @@ import static net.hostsharing.hsadminng.mapper.Array.insertNewEntriesAfterExisti
|
||||
@Setter
|
||||
public class PasswordProperty extends StringProperty<PasswordProperty> {
|
||||
|
||||
private static final String[] KEY_ORDER = insertNewEntriesAfterExistingEntry(StringProperty.KEY_ORDER, "computed", "hashedUsing");
|
||||
private static final String[] KEY_ORDER = insertNewEntriesAfterExistingEntry(
|
||||
StringProperty.KEY_ORDER,
|
||||
"computed",
|
||||
"hashedUsing");
|
||||
|
||||
private Algorithm hashedUsing;
|
||||
|
||||
@@ -34,10 +37,11 @@ public class PasswordProperty extends StringProperty<PasswordProperty> {
|
||||
|
||||
public PasswordProperty hashedUsing(final Algorithm algorithm) {
|
||||
this.hashedUsing = algorithm;
|
||||
computedBy((entity)
|
||||
-> ofNullable(entity.getDirectValue(propertyName, String.class))
|
||||
.map(password -> HashGenerator.using(algorithm).withRandomSalt().hash(password))
|
||||
.orElse(null));
|
||||
computedBy(
|
||||
ComputeMode.IN_PREP,
|
||||
(em, entity) -> ofNullable(entity.getDirectValue(propertyName, String.class))
|
||||
.map(password -> HashGenerator.using(algorithm).withRandomSalt().hash(password))
|
||||
.orElse(null));
|
||||
return self();
|
||||
}
|
||||
|
||||
@@ -69,9 +73,10 @@ public class PasswordProperty extends StringProperty<PasswordProperty> {
|
||||
}
|
||||
}
|
||||
|
||||
final long groupsCovered = Stream.of(hasLowerCase, hasUpperCase, hasDigit, hasSpecialChar).filter(v->v).count();
|
||||
if ( groupsCovered < 3) {
|
||||
result.add(propertyName + "' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters");
|
||||
final long groupsCovered = Stream.of(hasLowerCase, hasUpperCase, hasDigit, hasSpecialChar).filter(v -> v).count();
|
||||
if (groupsCovered < 3) {
|
||||
result.add(propertyName
|
||||
+ "' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters");
|
||||
}
|
||||
if (containsColon) {
|
||||
result.add(propertyName + "' must not contain colon (':')");
|
||||
|
@@ -4,6 +4,7 @@ import java.util.Map;
|
||||
|
||||
public interface PropertiesProvider {
|
||||
|
||||
boolean isLoaded();
|
||||
Map<String, Object> directProps();
|
||||
Object getContextValue(final String propName);
|
||||
|
||||
@@ -11,6 +12,10 @@ public interface PropertiesProvider {
|
||||
return cast(propName, directProps().get(propName), clazz, null);
|
||||
}
|
||||
|
||||
default <T> T getDirectValue(final String propName, final Class<T> clazz, final T defaultValue) {
|
||||
return cast(propName, directProps().get(propName), clazz, defaultValue);
|
||||
}
|
||||
|
||||
default <T> T getContextValue(final String propName, final Class<T> clazz) {
|
||||
return cast(propName, getContextValue(propName), clazz, null);
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.mapper.Array;
|
||||
import org.apache.commons.lang3.function.TriFunction;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -19,6 +20,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
@@ -46,11 +48,17 @@ public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T
|
||||
private Set<String> requiresAtMaxOneOf;
|
||||
private T defaultValue;
|
||||
|
||||
protected enum ComputeMode {
|
||||
IN_INIT,
|
||||
IN_PREP,
|
||||
IN_REVAMP
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
private Function<PropertiesProvider, T> computedBy;
|
||||
private BiFunction<EntityManager, 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
|
||||
private ComputeMode computed; // name 'computed' instead 'computeMode' for better readability in property description
|
||||
|
||||
@Accessors(makeFinal = true, chain = true, fluent = false)
|
||||
private boolean readOnly;
|
||||
@@ -75,7 +83,7 @@ public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]> function) {
|
||||
protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]> function) {
|
||||
this.deferredInit = function;
|
||||
}
|
||||
|
||||
@@ -95,7 +103,6 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
|
||||
|
||||
public P readOnly() {
|
||||
this.readOnly = true;
|
||||
optional();
|
||||
return self();
|
||||
}
|
||||
|
||||
@@ -137,8 +144,8 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
|
||||
if (asTotalLimitValidators == null) {
|
||||
asTotalLimitValidators = new ArrayList<>();
|
||||
}
|
||||
final TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> validator =
|
||||
(final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
final TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> validator =
|
||||
(final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
|
||||
final var total = entity.getSubBookingItems().stream()
|
||||
.map(server -> server.getResources().get(propertyName))
|
||||
@@ -169,11 +176,11 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
|
||||
return thresholdPercentage;
|
||||
}
|
||||
|
||||
public ValidatableProperty<P, T> eachComprising(final int factor, final TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> validator) {
|
||||
public ValidatableProperty<P, T> eachComprising(final int factor, final TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> validator) {
|
||||
if (asTotalLimitValidators == null) {
|
||||
asTotalLimitValidators = new ArrayList<>();
|
||||
}
|
||||
asTotalLimitValidators.add((final HsBookingItemEntity entity) -> validator.apply(entity, (IntegerProperty)this, factor));
|
||||
asTotalLimitValidators.add((final HsBookingItemEntity entity) -> validator.apply(entity, (IntegerProperty<?>)this, factor));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -235,8 +242,8 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
|
||||
protected abstract void validate(final List<String> result, final T propValue, final PropertiesProvider propProvider);
|
||||
|
||||
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
|
||||
if (required == null && requiresAtLeastOneOf == null && requiresAtMaxOneOf == null) {
|
||||
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)" );
|
||||
if (required == null && requiresAtLeastOneOf == null && requiresAtMaxOneOf == null && !readOnly && defaultValue == null) {
|
||||
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .readOnly(), .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)" );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,14 +308,26 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
|
||||
.toList();
|
||||
}
|
||||
|
||||
public P computedBy(final Function<PropertiesProvider, T> compute) {
|
||||
public P initializedBy(final BiFunction<EntityManager, PropertiesProvider, T> compute) {
|
||||
return computedBy(ComputeMode.IN_INIT, compute);
|
||||
}
|
||||
|
||||
public P renderedBy(final BiFunction<EntityManager, PropertiesProvider, T> compute) {
|
||||
return computedBy(ComputeMode.IN_REVAMP, compute);
|
||||
}
|
||||
|
||||
protected P computedBy(final ComputeMode computeMode, final BiFunction<EntityManager, PropertiesProvider, T> compute) {
|
||||
this.computedBy = compute;
|
||||
this.computed = true;
|
||||
this.computed = computeMode;
|
||||
return self();
|
||||
}
|
||||
|
||||
public <E extends PropertiesProvider> T compute(final E entity) {
|
||||
return computedBy.apply(entity);
|
||||
public boolean isComputed(final ComputeMode computeMode) {
|
||||
return computed == computeMode;
|
||||
}
|
||||
|
||||
public <E extends PropertiesProvider> T compute(final EntityManager em, final E entity) {
|
||||
return computedBy.apply(em, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.mapper;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -56,16 +57,19 @@ public class PatchableMapWrapper<T> implements Map<String, T> {
|
||||
return "{\n"
|
||||
+ (
|
||||
keySet().stream().sorted()
|
||||
.map(k -> " \"" + k + "\": " + optionallyQuoted(get(k))))
|
||||
.map(k -> " \"" + k + "\": " + formatted(get(k))))
|
||||
.collect(joining(",\n")
|
||||
)
|
||||
+ "\n}\n";
|
||||
}
|
||||
|
||||
private Object optionallyQuoted(final Object value) {
|
||||
if ( value instanceof Number || value instanceof Boolean ) {
|
||||
private Object formatted(final Object value) {
|
||||
if ( value == null || value instanceof Number || value instanceof Boolean ) {
|
||||
return value;
|
||||
}
|
||||
if ( value.getClass().isArray() ) {
|
||||
return "\"" + Arrays.toString( (Object[]) value) + "\"";
|
||||
}
|
||||
return "\"" + value + "\"";
|
||||
}
|
||||
|
||||
|
@@ -16,9 +16,8 @@ import static java.lang.Boolean.TRUE;
|
||||
|
||||
public final class Stringify<B> {
|
||||
|
||||
private final Class<B> clazz;
|
||||
private final String name;
|
||||
private Function<B, ?> idProp;
|
||||
private Function<? extends B, ?> idProp;
|
||||
private final List<Property<B>> props = new ArrayList<>();
|
||||
private String separator = ", ";
|
||||
private Boolean quotedValues = null;
|
||||
@@ -31,8 +30,16 @@ public final class Stringify<B> {
|
||||
return new Stringify<>(clazz, null);
|
||||
}
|
||||
|
||||
public <T extends B> Stringify<T> using(final Class<T> subClass) {
|
||||
//noinspection unchecked
|
||||
return (Stringify<T>) new Stringify<T>(subClass, null)
|
||||
.withIdProp(cast(idProp))
|
||||
.withProps(cast(props))
|
||||
.withSeparator(separator)
|
||||
.quotedValues(quotedValues);
|
||||
}
|
||||
|
||||
private Stringify(final Class<B> clazz, final String name) {
|
||||
this.clazz = clazz;
|
||||
if (name != null) {
|
||||
this.name = name;
|
||||
} else {
|
||||
@@ -45,7 +52,7 @@ public final class Stringify<B> {
|
||||
}
|
||||
}
|
||||
|
||||
public Stringify<B> withIdProp(final Function<B, ?> getter) {
|
||||
public Stringify<B> withIdProp(final Function<? extends B, ?> getter) {
|
||||
idProp = getter;
|
||||
return this;
|
||||
}
|
||||
@@ -60,6 +67,11 @@ public final class Stringify<B> {
|
||||
return this;
|
||||
}
|
||||
|
||||
private Stringify<B> withProps(final List<Property<B>> props) {
|
||||
this.props.addAll(props);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String apply(@NotNull B object) {
|
||||
final var propValues = props.stream()
|
||||
.map(prop -> PropertyValue.of(prop, prop.getter.apply(object)))
|
||||
@@ -74,7 +86,7 @@ public final class Stringify<B> {
|
||||
.map(propVal -> propName(propVal, "=") + optionallyQuoted(propVal))
|
||||
.collect(Collectors.joining(separator));
|
||||
return idProp != null
|
||||
? name + "(" + idProp.apply(object) + ": " + propValues + ")"
|
||||
? name + "(" + idProp.apply(cast(object)) + ": " + propValues + ")"
|
||||
: name + "(" + propValues + ")";
|
||||
}
|
||||
|
||||
@@ -106,6 +118,11 @@ public final class Stringify<B> {
|
||||
return this;
|
||||
}
|
||||
|
||||
private <T> T cast(final Object object) {
|
||||
//noinspection unchecked
|
||||
return (T)object;
|
||||
}
|
||||
|
||||
private record Property<B>(String name, Function<B, ?> getter) {}
|
||||
|
||||
private record PropertyValue<B>(Property<B> prop, Object rawValue, String value) {
|
||||
|
@@ -111,6 +111,21 @@ create trigger hs_hosting_asset_type_hierarchy_check_tg
|
||||
--//
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset hosting-asset-system-sequences:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS hs_hosting_asset_unixuser_system_id_seq
|
||||
AS integer
|
||||
MINVALUE 1000000
|
||||
MAXVALUE 9999999
|
||||
NO CYCLE
|
||||
OWNED BY NONE;
|
||||
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset hosting-asset-BOOKING-ITEM-HIERARCHY-CHECK:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
@@ -39,7 +39,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
|
||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||
"{type=string, propertyName=local-part, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$], required=true}",
|
||||
"{type=string, propertyName=sub-domain, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$]}",
|
||||
"{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}");
|
||||
"{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$], maxLength=320}, required=true, minLength=1}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,7 +73,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
|
||||
assertThat(result).containsExactlyInAnyOrder(
|
||||
"'EMAIL_ADDRESS:test@example.org.config.local-part' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowed' does not match",
|
||||
"'EMAIL_ADDRESS:test@example.org.config.sub-domain' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowedeither' does not match",
|
||||
"'EMAIL_ADDRESS:test@example.org.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");
|
||||
"'EMAIL_ADDRESS:test@example.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$] but 'garbage' does not match any");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -22,18 +22,24 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
|
||||
|
||||
// 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}");
|
||||
"{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$, ^:include:/.*$, ^\\|.*$, ^/dev/null$], maxLength=320}, required=true, minLength=1}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validatesValidEntity() {
|
||||
void acceptsValidEntity() {
|
||||
// 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"))
|
||||
entry("target", Array.of(
|
||||
"xyz00",
|
||||
"xyz00-abc",
|
||||
"office@example.com",
|
||||
"/dev/null",
|
||||
"|/home/pacs/xyz00/mailinglists/ecartis -s xyz00-intern"
|
||||
))
|
||||
))
|
||||
.build();
|
||||
final var validator = HostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
|
||||
@@ -46,14 +52,22 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void validatesProperties() {
|
||||
void rejectsInvalidConfig() {
|
||||
// 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"))
|
||||
entry("target", Array.of(
|
||||
"/dev/null",
|
||||
"xyz00",
|
||||
"xyz00-abc",
|
||||
"garbage",
|
||||
"office@example.com",
|
||||
":include:/home/pacs/xyz00/mailinglists/textfile",
|
||||
"|/home/pacs/xyz00/mailinglists/executable"
|
||||
))
|
||||
))
|
||||
.build();
|
||||
final var validator = HostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
|
||||
@@ -63,11 +77,11 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
|
||||
|
||||
// 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");
|
||||
"'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-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$, ^:include:/.*$, ^\\|.*$, ^/dev/null$] but 'garbage' does not match any");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validatesInvalidIdentifier() {
|
||||
void rejectsInvalidIndentifier() {
|
||||
// given
|
||||
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
|
||||
.type(EMAIL_ALIAS)
|
||||
@@ -84,7 +98,7 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
|
||||
|
||||
// then
|
||||
assertThat(result).containsExactlyInAnyOrder(
|
||||
"'identifier' expected to match '^xyz00$|^xyz00-[a-z0-9]+$', but is 'abc00-office'");
|
||||
"'identifier' expected to match '^xyz00$|^xyz00-[a-z0-9][a-z0-9\\._-]*$', but is 'abc00-office'");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -24,6 +25,8 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
|
||||
.caption("some valid test MariaDB-Instance")
|
||||
.build();
|
||||
|
||||
private EntityManager em = null; // not actually needed in these test cases
|
||||
|
||||
private static HsHostingAssetEntityBuilder givenValidMariaDbUserBuilder() {
|
||||
return HsHostingAssetEntity.builder()
|
||||
.type(MARIADB_USER)
|
||||
@@ -46,7 +49,7 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// then
|
||||
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
|
||||
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=true, hashedUsing=MYSQL_NATIVE, undisclosed=true}"
|
||||
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=IN_PREP, hashedUsing=MYSQL_NATIVE, undisclosed=true}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,7 +61,7 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// when
|
||||
// HashGenerator.nextSalt("Ly3LbsArtL5u4EVt"); // not needed for mysql_native_password
|
||||
validator.prepareProperties(givenMariaDbUserHostingAsset);
|
||||
validator.prepareProperties(em, givenMariaDbUserHostingAsset);
|
||||
|
||||
// then
|
||||
assertThat(givenMariaDbUserHostingAsset.getConfig()).containsExactlyInAnyOrderEntriesOf(ofEntries(
|
||||
|
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
@@ -27,6 +28,8 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest {
|
||||
.caption("some valid test PgSql-Instance")
|
||||
.build();
|
||||
|
||||
private EntityManager em = null; // not actually needed in these test cases
|
||||
|
||||
private static HsHostingAssetEntityBuilder givenValidMariaDbUserBuilder() {
|
||||
return HsHostingAssetEntity.builder()
|
||||
.type(PGSQL_USER)
|
||||
@@ -49,7 +52,7 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// then
|
||||
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
|
||||
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=true, hashedUsing=SCRAM_SHA256, undisclosed=true}"
|
||||
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=IN_PREP, hashedUsing=SCRAM_SHA256, undisclosed=true}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,7 +64,7 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// when
|
||||
HashGenerator.nextSalt(new String(Base64.getDecoder().decode("L1QxSVNyTU81b3NZS1djNg=="), Charset.forName("latin1")));
|
||||
validator.prepareProperties(givenMariaDbUserHostingAsset);
|
||||
validator.prepareProperties(em, givenMariaDbUserHostingAsset);
|
||||
|
||||
// then
|
||||
assertThat(givenMariaDbUserHostingAsset.getConfig()).containsExactlyInAnyOrderEntriesOf(ofEntries(
|
||||
|
@@ -3,8 +3,14 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.Query;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -15,7 +21,10 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANA
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
|
||||
import static net.hostsharing.hsadminng.mapper.PatchMap.entry;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
|
||||
private final HsHostingAssetEntity TEST_MANAGED_SERVER_HOSTING_ASSET = HsHostingAssetEntity.builder()
|
||||
@@ -43,6 +52,18 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
)))
|
||||
.build();
|
||||
|
||||
@Mock
|
||||
EntityManager em;
|
||||
|
||||
@BeforeEach
|
||||
void initMocks() {
|
||||
final var nativeQueryMock = mock(Query.class);
|
||||
lenient().when(nativeQueryMock.getSingleResult()).thenReturn(12345678);
|
||||
lenient().when(em.createNativeQuery("SELECT nextval('hs_hosting_asset_unixuser_system_id_seq')", Integer.class))
|
||||
.thenReturn(nativeQueryMock);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void preparesUnixUser() {
|
||||
// given
|
||||
@@ -51,14 +72,15 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// when
|
||||
HashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
|
||||
validator.prepareProperties(unixUserHostingAsset);
|
||||
validator.prepareProperties(em, unixUserHostingAsset);
|
||||
|
||||
// then
|
||||
assertThat(unixUserHostingAsset.getConfig()).containsExactlyInAnyOrderEntriesOf(ofEntries(
|
||||
entry("SSD hard quota", 50),
|
||||
entry("SSD soft quota", 40),
|
||||
entry("totpKey", "0x123456789abcdef01234"),
|
||||
entry("password", "$6$Ly3LbsArtL5u4EVt$i/ayIEvm0y4bjkFB6wbg8imbRIaw4mAA4gqYRVyoSkj.iIxJKS3KiRkSjP8gweNcpKL0Q0N31EadT8fCnWErL.")
|
||||
entry("password", "$6$Ly3LbsArtL5u4EVt$i/ayIEvm0y4bjkFB6wbg8imbRIaw4mAA4gqYRVyoSkj.iIxJKS3KiRkSjP8gweNcpKL0Q0N31EadT8fCnWErL."),
|
||||
entry("userid", 12345678)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -87,8 +109,8 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
.identifier("abc00-temp")
|
||||
.caption("some test UnixUser with invalid properties")
|
||||
.config(ofEntries(
|
||||
entry("SSD hard quota", 100),
|
||||
entry("SSD soft quota", 200),
|
||||
entry("SSD hard quota", 60000),
|
||||
entry("SSD soft quota", 70000),
|
||||
entry("HDD hard quota", 100),
|
||||
entry("HDD soft quota", 200),
|
||||
entry("shell", "/is/invalid"),
|
||||
@@ -104,11 +126,10 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// then
|
||||
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.SSD hard quota' is expected to be at most 51200 but is 60000",
|
||||
"'UNIX_USER:abc00-temp.config.SSD soft quota' is expected to be at most 60000 but is 70000",
|
||||
"'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'",
|
||||
"'UNIX_USER:abc00-temp.config.homedir' is readonly but given as '/is/read-only'",
|
||||
"'UNIX_USER:abc00-temp.config.totpKey' is expected to match any of [^0x([0-9A-Fa-f]{2})+$] but provided value does not match",
|
||||
"'UNIX_USER:abc00-temp.config.password' length is expected to be at min 8 but length of provided value is 5",
|
||||
@@ -131,7 +152,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// then
|
||||
assertThat(result).containsExactly(
|
||||
"'identifier' expected to match '^abc00$|^abc00-[a-z0-9]+$', but is 'xyz99-temp'");
|
||||
"'identifier' expected to match '^abc00$|^abc00-[a-z0-9\\._-]+$', but is 'xyz99-temp'");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -142,7 +163,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// when
|
||||
HashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
|
||||
final var result = validator.revampProperties(unixUserHostingAsset, unixUserHostingAsset.getConfig());
|
||||
final var result = validator.revampProperties(em, unixUserHostingAsset, unixUserHostingAsset.getConfig());
|
||||
|
||||
// then
|
||||
assertThat(result).containsExactlyInAnyOrderEntriesOf(ofEntries(
|
||||
@@ -162,14 +183,16 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
||||
|
||||
// then
|
||||
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
|
||||
"{type=integer, propertyName=SSD hard quota, unit=GB, maxFrom=SSD}",
|
||||
"{type=integer, propertyName=SSD soft quota, unit=GB, maxFrom=SSD hard quota}",
|
||||
"{type=integer, propertyName=HDD hard quota, unit=GB, maxFrom=HDD}",
|
||||
"{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=boolean, propertyName=locked, readOnly=true}",
|
||||
"{type=integer, propertyName=userid, readOnly=true, computed=IN_INIT}",
|
||||
"{type=integer, propertyName=SSD hard quota, unit=MB, maxFrom=SSD}",
|
||||
"{type=integer, propertyName=SSD soft quota, unit=MB, maxFrom=SSD hard quota}",
|
||||
"{type=integer, propertyName=HDD hard quota, unit=MB, maxFrom=HDD}",
|
||||
"{type=integer, propertyName=HDD soft quota, unit=MB, maxFrom=HDD hard quota}",
|
||||
"{type=string, propertyName=shell, provided=[/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=IN_REVAMP}",
|
||||
"{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=LINUX_SHA512, undisclosed=true}"
|
||||
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=IN_PREP, hashedUsing=LINUX_SHA512, undisclosed=true}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@ package net.hostsharing.hsadminng.hs.migration;
|
||||
import com.opencsv.CSVParserBuilder;
|
||||
import com.opencsv.CSVReader;
|
||||
import com.opencsv.CSVReaderBuilder;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
@@ -25,18 +27,24 @@ import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.Boolean.parseBoolean;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.mapper.Array.emptyArray;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
@@ -68,7 +76,7 @@ public class CsvDataImport extends ContextBasedTest {
|
||||
@MockBean
|
||||
HttpServletRequest request;
|
||||
|
||||
private static final List<AssertionError> errors = new ArrayList<>();
|
||||
static final List<String> errors = new ArrayList<>();
|
||||
|
||||
public List<String[]> readAllLines(Reader reader) throws Exception {
|
||||
|
||||
@@ -113,6 +121,16 @@ public class CsvDataImport extends ContextBasedTest {
|
||||
return records.subList(1, records.size());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String[] parseCsvLine(final String csvLine) {
|
||||
try (final var reader = new CSVReader(new StringReader(csvLine))) {
|
||||
return stream(ofNullable(reader.readNext()).orElse(emptyArray(String.class)))
|
||||
.map(String::trim)
|
||||
.map(target -> target.startsWith("'") && target.endsWith("'") ? target.substring(1, target.length()-1) : target)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
String[] trimAll(final String[] record) {
|
||||
for (int i = 0; i < record.length; ++i) {
|
||||
if (record[i] != null) {
|
||||
@@ -124,23 +142,75 @@ public class CsvDataImport extends ContextBasedTest {
|
||||
|
||||
public <T extends RbacObject> T persist(final Integer id, final T entity) {
|
||||
try {
|
||||
final var asString = entity.toString();
|
||||
if ( asString.contains("'null null, null'") || asString.equals("person()")) {
|
||||
System.err.println("skipping to persist empty record-id " + id + " #" + entity.hashCode() + ": " + entity);
|
||||
return entity;
|
||||
if (entity instanceof HsHostingAsset ha) {
|
||||
//noinspection unchecked
|
||||
return (T) persistViaSql(id, ha);
|
||||
}
|
||||
//System.out.println("persisting #" + entity.hashCode() + ": " + entity);
|
||||
em.persist(entity);
|
||||
// uncomment for debugging purposes
|
||||
// em.flush(); // makes it slow, but produces better error messages
|
||||
// System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid());
|
||||
return persistViaEM(id, entity);
|
||||
} catch (Exception exc) {
|
||||
System.err.println("failed to persist #" + entity.hashCode() + ": " + entity);
|
||||
System.err.println(exc);
|
||||
errors.add("failed to persist #" + entity.hashCode() + ": " + entity);
|
||||
errors.add(exc.toString());
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
public <T extends RbacObject> T persistViaEM(final Integer id, final T entity) {
|
||||
//System.out.println("persisting #" + entity.hashCode() + ": " + entity);
|
||||
em.persist(entity);
|
||||
// uncomment for debugging purposes
|
||||
// em.flush(); // makes it slow, but produces better error messages
|
||||
// System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid());
|
||||
return entity;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public RbacObject<HsHostingAsset> persistViaSql(final Integer id, final HsHostingAsset entity) {
|
||||
if (entity.getUuid() == null) {
|
||||
entity.setUuid(UUID.randomUUID());
|
||||
}
|
||||
|
||||
final var query = em.createNativeQuery("""
|
||||
insert into hs_hosting_asset(
|
||||
uuid,
|
||||
type,
|
||||
bookingitemuuid,
|
||||
parentassetuuid,
|
||||
assignedtoassetuuid,
|
||||
alarmcontactuuid,
|
||||
identifier,
|
||||
caption,
|
||||
config,
|
||||
version)
|
||||
values (
|
||||
:uuid,
|
||||
:type,
|
||||
:bookingitemuuid,
|
||||
:parentassetuuid,
|
||||
:assignedtoassetuuid,
|
||||
:alarmcontactuuid,
|
||||
:identifier,
|
||||
:caption,
|
||||
cast(:config as jsonb),
|
||||
:version)
|
||||
""")
|
||||
.setParameter("uuid", entity.getUuid())
|
||||
.setParameter("type", entity.getType().name())
|
||||
.setParameter("bookingitemuuid", ofNullable(entity.getBookingItem()).map(RbacObject::getUuid).orElse(null))
|
||||
.setParameter("parentassetuuid", ofNullable(entity.getParentAsset()).map(RbacObject::getUuid).orElse(null))
|
||||
.setParameter("assignedtoassetuuid", ofNullable(entity.getAssignedToAsset()).map(RbacObject::getUuid).orElse(null))
|
||||
.setParameter("alarmcontactuuid", ofNullable(entity.getAlarmContact()).map(RbacObject::getUuid).orElse(null))
|
||||
.setParameter("identifier", entity.getIdentifier())
|
||||
.setParameter("caption", entity.getCaption())
|
||||
.setParameter("config", entity.getConfig().toString())
|
||||
.setParameter("version", entity.getVersion());
|
||||
|
||||
final var count = query.executeUpdate();
|
||||
logError(() -> {
|
||||
assertThat(count).isEqualTo(1);
|
||||
});
|
||||
return entity;
|
||||
}
|
||||
|
||||
protected <E> String toFormattedString(final Map<Integer, E> map) {
|
||||
if ( map.isEmpty() ) {
|
||||
return "{}";
|
||||
@@ -215,12 +285,33 @@ public class CsvDataImport extends ContextBasedTest {
|
||||
try {
|
||||
assertion.run();
|
||||
} catch (final AssertionError exc) {
|
||||
errors.add(exc);
|
||||
errors.add(exc.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void logErrors() {
|
||||
assumeThat(errors).isEmpty();
|
||||
assertThat(errors).isEmpty();
|
||||
}
|
||||
|
||||
void expectErrors(final String... expectedErrors) {
|
||||
assertContainsExactlyInAnyOrderIgnoringWhitespace(errors, expectedErrors);
|
||||
}
|
||||
|
||||
private static class IgnoringWhitespaceComparator implements Comparator<String> {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
return s1.replaceAll("\\s", "").compareTo(s2.replaceAll("\\s", ""));
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertContainsExactlyInAnyOrderIgnoringWhitespace(final List<String> expected, final List<String> actual) {
|
||||
final var sortedExpected = expected.stream().map(m -> m.replaceAll("\\s", "")).toList();
|
||||
final var sortedActual = actual.stream().map(m -> m.replaceAll("\\s", "")).toArray(String[]::new);
|
||||
assertThat(sortedExpected).containsExactlyInAnyOrder(sortedActual);
|
||||
}
|
||||
|
||||
public static void assertContainsExactlyInAnyOrderIgnoringWhitespace(final List<String> expected, final String... actual) {
|
||||
assertContainsExactlyInAnyOrderIgnoringWhitespace(expected, asList(actual));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +343,7 @@ class Record {
|
||||
}
|
||||
|
||||
String getString(final String columnName) {
|
||||
return row[columns.indexOf(columnName)];
|
||||
return row[columns.indexOf(columnName)].trim();
|
||||
}
|
||||
|
||||
boolean isEmpty(final String columnName) {
|
||||
@@ -288,12 +379,17 @@ class Record {
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ContinueOnFailure {
|
||||
}
|
||||
|
||||
class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback {
|
||||
|
||||
private static boolean previousTestsPassed = true;
|
||||
|
||||
public void testFailed(ExtensionContext context, Throwable cause) {
|
||||
previousTestsPassed = false;
|
||||
@Override
|
||||
public void testFailed(final ExtensionContext context, final Throwable cause) {
|
||||
previousTestsPassed = previousTestsPassed && context.getElement().map(e -> e.isAnnotationPresent(ContinueOnFailure.class)).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,114 @@
|
||||
package net.hostsharing.hsadminng.hs.migration;
|
||||
|
||||
import io.hypersistence.utils.hibernate.type.json.JsonType;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.Version;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Builder
|
||||
@Entity
|
||||
@Table(name = "hs_hosting_asset")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsHostingAssetRawEntity implements HsHostingAsset {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "bookingitemuuid")
|
||||
private HsBookingItemEntity bookingItem;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "parentassetuuid")
|
||||
private HsHostingAssetRawEntity parentAsset;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "assignedtoassetuuid")
|
||||
private HsHostingAssetRawEntity assignedToAsset;
|
||||
|
||||
@Column(name = "type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private HsHostingAssetType type;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "alarmcontactuuid")
|
||||
private HsOfficeContactEntity alarmContact;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
|
||||
private List<HsHostingAssetRawEntity> subHostingAssets;
|
||||
|
||||
@Column(name = "identifier")
|
||||
private String identifier; // e.g. vm1234, xyz00, example.org, xyz00_abc
|
||||
|
||||
@Column(name = "caption")
|
||||
private String caption;
|
||||
|
||||
@Builder.Default
|
||||
@Setter(AccessLevel.NONE)
|
||||
@Type(JsonType.class)
|
||||
@Column(columnDefinition = "config")
|
||||
private Map<String, Object> config = new HashMap<>();
|
||||
|
||||
@Transient
|
||||
private PatchableMapWrapper<Object> configWrapper;
|
||||
|
||||
@Transient
|
||||
private boolean isLoaded;
|
||||
|
||||
@PostLoad
|
||||
public void markAsLoaded() {
|
||||
this.isLoaded = true;
|
||||
}
|
||||
|
||||
public PatchableMapWrapper<Object> getConfig() {
|
||||
return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> directProps() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.using(HsHostingAssetRawEntity.class).apply(this);
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
@@ -23,18 +22,23 @@ import org.springframework.test.annotation.Commit;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Map.entry;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ALIAS;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.IPV4_NUMBER;
|
||||
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;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
@@ -91,13 +95,15 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
static final Integer IP_NUMBER_ID_OFFSET = 1000000;
|
||||
static final Integer HIVE_ID_OFFSET = 2000000;
|
||||
static final Integer PACKET_ID_OFFSET = 3000000;
|
||||
static final Integer UNIXUSER_ID_OFFSET = 4000000;
|
||||
static final Integer EMAILALIAS_ID_OFFSET = 5000000;
|
||||
|
||||
record Hive(int hive_id, String hive_name, int inet_addr_id, AtomicReference<HsHostingAssetEntity> serverRef) {}
|
||||
record Hive(int hive_id, String hive_name, int inet_addr_id, AtomicReference<HsHostingAssetRawEntity> serverRef) {}
|
||||
|
||||
static Map<Integer, HsBookingProjectEntity> bookingProjects = new WriteOnceMap<>();
|
||||
static Map<Integer, HsBookingItemEntity> bookingItems = new WriteOnceMap<>();
|
||||
static Map<Integer, Hive> hives = new WriteOnceMap<>();
|
||||
static Map<Integer, HsHostingAssetEntity> hostingAssets = new WriteOnceMap<>(); // TODO.impl: separate maps for each type?
|
||||
static Map<Integer, HsHostingAssetRawEntity> hostingAssets = new WriteOnceMap<>(); // TODO.impl: separate maps for each type?
|
||||
|
||||
@Test
|
||||
@Order(11010)
|
||||
@@ -126,14 +132,13 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
void verifyIpNumbers() {
|
||||
assumeThatWeAreImportingControlledTestData();
|
||||
|
||||
// no contacts yet => mostly null values
|
||||
assertThat(firstOfEachType(5, IPV4_NUMBER)).isEqualToIgnoringWhitespace("""
|
||||
{
|
||||
1000363=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.34),
|
||||
1000381=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.52),
|
||||
1000402=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.73),
|
||||
1000433=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.104),
|
||||
1000457=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.128)
|
||||
1000363=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.34),
|
||||
1000381=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.52),
|
||||
1000402=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.73),
|
||||
1000433=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.104),
|
||||
1000457=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.128)
|
||||
}
|
||||
""");
|
||||
}
|
||||
@@ -154,7 +159,6 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
void verifyHives() {
|
||||
assumeThatWeAreImportingControlledTestData();
|
||||
|
||||
// no contacts yet => mostly null values
|
||||
assertThat(toFormattedString(first(5, hives))).isEqualToIgnoringWhitespace("""
|
||||
{
|
||||
2000001=Hive[hive_id=1, hive_name=h00, inet_addr_id=358, serverRef=null],
|
||||
@@ -184,13 +188,13 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
|
||||
assertThat(firstOfEachType(3, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE)).isEqualToIgnoringWhitespace("""
|
||||
{
|
||||
3000630=HsHostingAssetEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
|
||||
3000968=HsHostingAssetEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
|
||||
3000978=HsHostingAssetEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
|
||||
3001061=HsHostingAssetEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
|
||||
3001094=HsHostingAssetEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
|
||||
3001112=HsHostingAssetEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
|
||||
3023611=HsHostingAssetEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
|
||||
3000630=HsHostingAssetRawEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
|
||||
3000968=HsHostingAssetRawEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
|
||||
3000978=HsHostingAssetRawEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
|
||||
3001061=HsHostingAssetRawEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
|
||||
3001094=HsHostingAssetRawEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
|
||||
3001112=HsHostingAssetRawEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
|
||||
3023611=HsHostingAssetRawEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
|
||||
}
|
||||
""");
|
||||
assertThat(firstOfEachType(
|
||||
@@ -226,19 +230,18 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
void verifyPacketComponents() {
|
||||
assumeThatWeAreImportingControlledTestData();
|
||||
|
||||
// no contacts yet => mostly null values
|
||||
assertThat(firstOfEachType(5, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE))
|
||||
.isEqualToIgnoringWhitespace("""
|
||||
{
|
||||
3000630=HsHostingAssetEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
|
||||
3000968=HsHostingAssetEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
|
||||
3000978=HsHostingAssetEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
|
||||
3001061=HsHostingAssetEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
|
||||
3001094=HsHostingAssetEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
|
||||
3001112=HsHostingAssetEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
|
||||
3001447=HsHostingAssetEntity(MANAGED_SERVER, vm1093, HA vm1093, D-1000000:hsh default project:BI vm1093),
|
||||
3019959=HsHostingAssetEntity(MANAGED_WEBSPACE, dph00, HA dph00, MANAGED_SERVER:vm1093, D-1101900:dph default project:BI dph00),
|
||||
3023611=HsHostingAssetEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
|
||||
3000630=HsHostingAssetRawEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
|
||||
3000968=HsHostingAssetRawEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
|
||||
3000978=HsHostingAssetRawEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
|
||||
3001061=HsHostingAssetRawEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
|
||||
3001094=HsHostingAssetRawEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
|
||||
3001112=HsHostingAssetRawEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
|
||||
3001447=HsHostingAssetRawEntity(MANAGED_SERVER, vm1093, HA vm1093, D-1000000:hsh default project:BI vm1093),
|
||||
3019959=HsHostingAssetRawEntity(MANAGED_WEBSPACE, dph00, HA dph00, MANAGED_SERVER:vm1093, D-1101900:dph default project:BI dph00),
|
||||
3023611=HsHostingAssetRawEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
|
||||
}
|
||||
""");
|
||||
assertThat(firstOfEachType(
|
||||
@@ -262,58 +265,247 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(11400)
|
||||
@Order(14010)
|
||||
void importUnixUsers() {
|
||||
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/unixuser.csv")) {
|
||||
final var lines = readAllLines(reader);
|
||||
importUnixUsers(justHeader(lines), withoutHeader(lines));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(14019)
|
||||
void verifyUnixUsers() {
|
||||
assumeThatWeAreImportingControlledTestData();
|
||||
|
||||
assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace("""
|
||||
{
|
||||
4005803=HsHostingAssetRawEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102090}),
|
||||
4005805=HsHostingAssetRawEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102091}),
|
||||
4005809=HsHostingAssetRawEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 8, "SSD soft quota": 4, "locked": false, "shell": "/bin/bash", "userid": 102093}),
|
||||
4005811=HsHostingAssetRawEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102094}),
|
||||
4005813=HsHostingAssetRawEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102095}),
|
||||
4005835=HsHostingAssetRawEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 1024, "SSD soft quota": 1024, "locked": false, "shell": "/usr/bin/passwd", "userid": 102106}),
|
||||
4005964=HsHostingAssetRawEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102147}),
|
||||
4005966=HsHostingAssetRawEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "shell": "/bin/bash", "userid": 102148}),
|
||||
4005990=HsHostingAssetRawEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102160}),
|
||||
4100705=HsHostingAssetRawEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 10003}),
|
||||
4100824=HsHostingAssetRawEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 10000}),
|
||||
4167846=HsHostingAssetRawEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 110568}),
|
||||
4169546=HsHostingAssetRawEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110593}),
|
||||
4169596=HsHostingAssetRawEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110594})
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(14020)
|
||||
void importEmailAliases() {
|
||||
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/emailalias.csv")) {
|
||||
final var lines = readAllLines(reader);
|
||||
importEmailAliases(justHeader(lines), withoutHeader(lines));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(14029)
|
||||
void verifyEmailAliases() {
|
||||
assumeThatWeAreImportingControlledTestData();
|
||||
|
||||
assertThat(firstOfEachType(15, EMAIL_ALIAS)).isEqualToIgnoringWhitespace("""
|
||||
{
|
||||
5002403=HsHostingAssetRawEntity(EMAIL_ALIAS, lug00, lug00, MANAGED_WEBSPACE:lug00, { "target": "[michael.mellis@example.com]"}),
|
||||
5002405=HsHostingAssetRawEntity(EMAIL_ALIAS, lug00-wla-listar, lug00-wla-listar, MANAGED_WEBSPACE:lug00, { "target": "[|/home/pacs/lug00/users/in/mailinglist/listar]"}),
|
||||
5002429=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00, mim00, MANAGED_WEBSPACE:mim00, { "target": "[mim12-mi@mim12.hostsharing.net]"}),
|
||||
5002431=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-abruf, mim00-abruf, MANAGED_WEBSPACE:mim00, { "target": "[michael.mellis@hostsharing.net]"}),
|
||||
5002449=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-hhfx, mim00-hhfx, MANAGED_WEBSPACE:mim00, { "target": "[mim00-hhfx, |/usr/bin/formail -I 'Reply-To: hamburger-fx@example.net' | /usr/lib/sendmail mim00-hhfx-l]"}),
|
||||
5002451=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-hhfx-l, mim00-hhfx-l, MANAGED_WEBSPACE:mim00, { "target": "[:include:/home/pacs/mim00/etc/hhfx.list]"}),
|
||||
5002452=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-empty, mim00-empty, MANAGED_WEBSPACE:mim00, { "target": "[]"}),
|
||||
5002453=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-0_entries, mim00-0_entries, MANAGED_WEBSPACE:mim00, { "target": "[]"}),
|
||||
5002454=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-dev.null, mim00-dev.null, MANAGED_WEBSPACE:mim00, { "target": "[/dev/null]"}),
|
||||
5002455=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-1_with_space, mim00-1_with_space, MANAGED_WEBSPACE:mim00, { "target": "[|/home/pacs/mim00/install/corpslistar/listar]"}),
|
||||
5002456=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-1_with_single_quotes, mim00-1_with_single_quotes, MANAGED_WEBSPACE:mim00, { "target": "[|/home/pacs/rir00/mailinglist/ecartis -r kybs06-intern]"})
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@Order(18010)
|
||||
void validateBookingItems() {
|
||||
bookingItems.forEach((id, bi) -> {
|
||||
try {
|
||||
HsBookingItemEntityValidatorRegistry.validated(bi);
|
||||
} catch (final Exception exc) {
|
||||
System.err.println("validation failed for id:" + id + "( " + bi + "): " + exc.getMessage());
|
||||
errors.add("validation failed for id:" + id + "( " + bi + "): " + exc.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(11410)
|
||||
@Order(18020)
|
||||
void validateHostingAssets() {
|
||||
hostingAssets.forEach((id, ha) -> {
|
||||
try {
|
||||
new HostingAssetEntitySaveProcessor(ha)
|
||||
new HostingAssetEntitySaveProcessor(em, ha)
|
||||
.preprocessEntity()
|
||||
.validateEntity();
|
||||
.validateEntity()
|
||||
.prepareForSave();
|
||||
} catch (final Exception exc) {
|
||||
System.err.println("validation failed for id:" + id + "( " + ha + "): " + exc.getMessage());
|
||||
errors.add("validation failed for id:" + id + "( " + ha + "): " + exc.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(18999)
|
||||
@ContinueOnFailure
|
||||
void logValidationErrors() {
|
||||
this.logErrors();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@Order(19000)
|
||||
@Commit
|
||||
void persistHostingAssetEntities() {
|
||||
void persistBookingProjects() {
|
||||
|
||||
System.out.println("PERSISTING hosting-assets to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
System.out.println("PERSISTING booking-projects to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
|
||||
jpaAttempt.transacted(() -> {
|
||||
context(rbacSuperuser);
|
||||
bookingProjects.forEach(this::persist);
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19010)
|
||||
@Commit
|
||||
void persistBookingItems() {
|
||||
|
||||
System.out.println("PERSISTING booking-items to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
|
||||
jpaAttempt.transacted(() -> {
|
||||
context(rbacSuperuser);
|
||||
bookingItems.forEach(this::persistRecursively);
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19120)
|
||||
@Commit
|
||||
void persistCloudServers() {
|
||||
|
||||
System.out.println("PERSISTING cloud-servers to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
|
||||
persistHostingAssetsOfType(CLOUD_SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19130)
|
||||
@Commit
|
||||
void persistManagedServers() {
|
||||
System.out.println("PERSISTING managed-servers to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
persistHostingAssetsOfType(MANAGED_SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19140)
|
||||
@Commit
|
||||
void persistManagedWebspaces() {
|
||||
System.out.println("PERSISTING managed-webspaces to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
persistHostingAssetsOfType(MANAGED_WEBSPACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19150)
|
||||
@Commit
|
||||
void persistIPNumbers() {
|
||||
System.out.println("PERSISTING ip-numbers to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
persistHostingAssetsOfType(IPV4_NUMBER);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19160)
|
||||
@Commit
|
||||
void persistUnixUsers() {
|
||||
System.out.println("PERSISTING unix-users to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
persistHostingAssetsOfType(UNIX_USER);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19170)
|
||||
@Commit
|
||||
void persistEmailAliases() {
|
||||
System.out.println("PERSISTING email-aliases to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
|
||||
persistHostingAssetsOfType(EMAIL_ALIAS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19900)
|
||||
void verifyPersistedUnixUsersWithUserId() {
|
||||
assumeThatWeAreImportingControlledTestData();
|
||||
|
||||
assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace("""
|
||||
{
|
||||
4005803=HsHostingAssetRawEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102090}),
|
||||
4005805=HsHostingAssetRawEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102091}),
|
||||
4005809=HsHostingAssetRawEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 8, "SSD soft quota": 4, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102093}),
|
||||
4005811=HsHostingAssetRawEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102094}),
|
||||
4005813=HsHostingAssetRawEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102095}),
|
||||
4005835=HsHostingAssetRawEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 1024, "SSD soft quota": 1024, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102106}),
|
||||
4005964=HsHostingAssetRawEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102147}),
|
||||
4005966=HsHostingAssetRawEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102148}),
|
||||
4005990=HsHostingAssetRawEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102160}),
|
||||
4100705=HsHostingAssetRawEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/false", "userid": 10003}),
|
||||
4100824=HsHostingAssetRawEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 10000}),
|
||||
4167846=HsHostingAssetRawEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/false", "userid": 110568}),
|
||||
4169546=HsHostingAssetRawEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 110593}),
|
||||
4169596=HsHostingAssetRawEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 110594})
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19910)
|
||||
void verifyBookingItemsAreActuallyPersisted() {
|
||||
final var biCount = (Integer) em.createNativeQuery("SELECT count(*) FROM hs_booking_item", Integer.class).getSingleResult();
|
||||
assertThat(biCount).isGreaterThan(isImportingControlledTestData() ? 5 : 500);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(19920)
|
||||
void verifyHostingAssetsAreActuallyPersisted() {
|
||||
final var haCount = (Integer) em.createNativeQuery("SELECT count(*) FROM hs_hosting_asset", Integer.class).getSingleResult();
|
||||
assertThat(haCount).isGreaterThan(isImportingControlledTestData() ? 20 : 10000);
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
|
||||
@Test
|
||||
@Order(99999)
|
||||
void logErrors() {
|
||||
super.logErrors();
|
||||
if (isImportingControlledTestData()) {
|
||||
super.expectErrors("""
|
||||
validation failed for id:5002452( HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-empty, mim00-empty, MANAGED_WEBSPACE:mim00, {
|
||||
"target": "[]"
|
||||
}
|
||||
)): ['EMAIL_ALIAS:mim00-empty.config.target' length is expected to be at min 1 but length of [[]] is 0]""",
|
||||
"""
|
||||
validation failed for id:5002453( HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-0_entries, mim00-0_entries, MANAGED_WEBSPACE:mim00, {
|
||||
"target": "[]"
|
||||
}
|
||||
)): ['EMAIL_ALIAS:mim00-0_entries.config.target' length is expected to be at min 1 but length of [[]] is 0]"""
|
||||
);
|
||||
} else {
|
||||
super.logErrors();
|
||||
}
|
||||
}
|
||||
|
||||
private void persistRecursively(final Integer key, final HsBookingItemEntity bi) {
|
||||
@@ -323,19 +515,21 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
persist(key, HsBookingItemEntityValidatorRegistry.validated(bi));
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
|
||||
private void persistHostingAssetsOfType(final HsHostingAssetType hsHostingAssetType) {
|
||||
jpaAttempt.transacted(() -> {
|
||||
context(rbacSuperuser);
|
||||
hostingAssets.forEach((key, ha) -> {
|
||||
if (ha.getType() == hsHostingAssetType) {
|
||||
new HostingAssetEntitySaveProcessor(ha)
|
||||
.preprocessEntity()
|
||||
.validateEntity()
|
||||
.prepareForSave()
|
||||
.saveUsing(entity -> persist(key, entity))
|
||||
.validateContext();
|
||||
context(rbacSuperuser);
|
||||
if (ha.getType() == hsHostingAssetType) {
|
||||
new HostingAssetEntitySaveProcessor(em, ha)
|
||||
.preprocessEntity()
|
||||
.validateEntityIgnoring("'EMAIL_ALIAS:.*\\.config\\.target' .*")
|
||||
.prepareForSave()
|
||||
.saveUsing(entity -> persist(key, entity))
|
||||
.validateContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}).assertSuccessful();
|
||||
}
|
||||
@@ -346,7 +540,7 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
.map(this::trimAll)
|
||||
.map(row -> new Record(columns, row))
|
||||
.forEach(rec -> {
|
||||
final var ipNumber = HsHostingAssetEntity.builder()
|
||||
final var ipNumber = HsHostingAssetRawEntity.builder()
|
||||
.type(IPV4_NUMBER)
|
||||
.identifier(rec.getString("inet_addr"))
|
||||
.caption(rec.getString("description"))
|
||||
@@ -402,12 +596,17 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
bookingItems.put(PACKET_ID_OFFSET + packet_id, bookingItem);
|
||||
final var haType = determineHaType(basepacket_code);
|
||||
|
||||
logError(() -> assertThat(!free || haType == MANAGED_WEBSPACE || bookingItem.getRelatedProject().getDebitor().getDefaultPrefix().equals("hsh"))
|
||||
.as("packet.free only supported for Hostsharing-Assets and ManagedWebspace in customer-ManagedServer, but is set for " + packet_name)
|
||||
logError(() -> assertThat(!free || haType == MANAGED_WEBSPACE || bookingItem.getRelatedProject()
|
||||
.getDebitor()
|
||||
.getDefaultPrefix()
|
||||
.equals("hsh"))
|
||||
.as("packet.free only supported for Hostsharing-Assets and ManagedWebspace in customer-ManagedServer, but is set for "
|
||||
+ packet_name)
|
||||
.isTrue());
|
||||
|
||||
final var asset = HsHostingAssetEntity.builder()
|
||||
.isLoaded(haType == MANAGED_WEBSPACE) // this turns off identifier validation to accept former default prefixes
|
||||
final var asset = HsHostingAssetRawEntity.builder()
|
||||
// this turns off identifier validation to accept former default prefixes
|
||||
.isLoaded(haType == MANAGED_WEBSPACE)
|
||||
.type(haType)
|
||||
.identifier(packet_name)
|
||||
.bookingItem(bookingItem)
|
||||
@@ -461,9 +660,9 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
case "DAEMON" -> "Daemons";
|
||||
case "MULTI" -> "Multi";
|
||||
case "CPU" -> "CPU";
|
||||
case "RAM" -> returning("RAM", convert = v -> v/1024);
|
||||
case "QUOTA" -> returning("SSD", convert = v -> v/1024);
|
||||
case "STORAGE" -> returning("HDD", convert = v -> v/1024);
|
||||
case "RAM" -> returning("RAM", convert = v -> v / 1024);
|
||||
case "QUOTA" -> returning("SSD", convert = v -> v / 1024);
|
||||
case "STORAGE" -> returning("HDD", convert = v -> v / 1024);
|
||||
case "TRAFFIC" -> "Traffic";
|
||||
case "OFFICE" -> returning("Online Office Server", convert = v -> v == 1);
|
||||
|
||||
@@ -526,7 +725,7 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
case "SLAPLAT8H" -> "EXT8H";
|
||||
default -> throw new IllegalArgumentException("unknown basecomponent_code: " + basecomponent_code);
|
||||
};
|
||||
if ( ofNullable(asset.getBookingItem().getResources().get(name)).map("BASIC"::equals).orElse(true) ) {
|
||||
if (ofNullable(asset.getBookingItem().getResources().get(name)).map("BASIC"::equals).orElse(true)) {
|
||||
asset.getBookingItem().getResources().put(name, slaValue);
|
||||
}
|
||||
} else if (name.startsWith("SLA")) {
|
||||
@@ -537,7 +736,90 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
});
|
||||
}
|
||||
|
||||
<V> V returning(final V value, final Object... assignments) {
|
||||
private void importUnixUsers(final String[] header, final List<String[]> records) {
|
||||
final var columns = new Columns(header);
|
||||
records.stream()
|
||||
.map(this::trimAll)
|
||||
.map(row -> new Record(columns, row))
|
||||
.forEach(rec -> {
|
||||
final var unixuser_id = rec.getInteger("unixuser_id");
|
||||
final var packet_id = rec.getInteger("packet_id");
|
||||
final var unixUserAsset = HsHostingAssetRawEntity.builder()
|
||||
.type(UNIX_USER)
|
||||
.parentAsset(hostingAssets.get(PACKET_ID_OFFSET + packet_id))
|
||||
.identifier(rec.getString("name"))
|
||||
.caption(rec.getString("comment"))
|
||||
.isLoaded(true) // avoid overwriting imported userids with generated ids
|
||||
.config(new HashMap<>(Map.ofEntries(
|
||||
entry("shell", rec.getString("shell")),
|
||||
// entry("homedir", rec.getString("homedir")), do not import, it's calculated
|
||||
entry("locked", rec.getBoolean("locked")),
|
||||
entry("userid", rec.getInteger("userid")),
|
||||
entry("SSD soft quota", rec.getInteger("quota_softlimit")),
|
||||
entry("SSD hard quota", rec.getInteger("quota_hardlimit")),
|
||||
entry("HDD soft quota", rec.getInteger("storage_softlimit")),
|
||||
entry("HDD hard quota", rec.getInteger("storage_hardlimit"))
|
||||
)))
|
||||
.build();
|
||||
|
||||
// TODO.spec: crop SSD+HDD limits if > booked
|
||||
if (unixUserAsset.getDirectValue("SSD hard quota", Integer.class, 0)
|
||||
> 1024*unixUserAsset.getContextValue("SSD", Integer.class, 0)) {
|
||||
unixUserAsset.getConfig().put("SSD hard quota", unixUserAsset.getContextValue("SSD", Integer.class, 0)*1024);
|
||||
}
|
||||
if (unixUserAsset.getDirectValue("HDD hard quota", Integer.class, 0)
|
||||
> 1024*unixUserAsset.getContextValue("HDD", Integer.class, 0)) {
|
||||
unixUserAsset.getConfig().put("HDD hard quota", unixUserAsset.getContextValue("HDD", Integer.class, 0)*1024);
|
||||
}
|
||||
|
||||
// TODO.spec: does `softlimit<hardlimit?` even make sense? Fix it in this or the other direction?
|
||||
if (unixUserAsset.getDirectValue("SSD soft quota", Integer.class, 0)
|
||||
> unixUserAsset.getDirectValue("SSD hard quota", Integer.class, 0)) {
|
||||
unixUserAsset.getConfig().put("SSD soft quota", unixUserAsset.getConfig().get("SSD hard quota"));
|
||||
}
|
||||
if (unixUserAsset.getDirectValue("HDD soft quota", Integer.class, 0)
|
||||
> unixUserAsset.getDirectValue("HDD hard quota", Integer.class, 0)) {
|
||||
unixUserAsset.getConfig().put("HDD soft quota", unixUserAsset.getConfig().get("HDD hard quota"));
|
||||
}
|
||||
|
||||
// TODO.spec: remove HDD limits if no HDD storage is booked
|
||||
if (unixUserAsset.getContextValue("HDD", Integer.class, 0) == 0) {
|
||||
unixUserAsset.getConfig().remove("HDD hard quota");
|
||||
unixUserAsset.getConfig().remove("HDD soft quota");
|
||||
}
|
||||
|
||||
hostingAssets.put(UNIXUSER_ID_OFFSET + unixuser_id, unixUserAsset);
|
||||
});
|
||||
}
|
||||
|
||||
private void importEmailAliases(final String[] header, final List<String[]> records) {
|
||||
final var columns = new Columns(header);
|
||||
records.stream()
|
||||
.map(this::trimAll)
|
||||
.map(row -> new Record(columns, row))
|
||||
.forEach(rec -> {
|
||||
final var unixuser_id = rec.getInteger("emailalias_id");
|
||||
final var packet_id = rec.getInteger("pac_id");
|
||||
final var targets = parseCsvLine(rec.getString("target"));
|
||||
final var unixUserAsset = HsHostingAssetRawEntity.builder()
|
||||
.type(EMAIL_ALIAS)
|
||||
.parentAsset(hostingAssets.get(PACKET_ID_OFFSET + packet_id))
|
||||
.identifier(rec.getString("name"))
|
||||
.caption(rec.getString("name"))
|
||||
.config(Map.ofEntries(
|
||||
entry("target", targets)
|
||||
))
|
||||
.build();
|
||||
hostingAssets.put(EMAILALIAS_ID_OFFSET + unixuser_id, unixUserAsset);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
|
||||
<V> V returning(
|
||||
final V value,
|
||||
@SuppressWarnings("unused") final Object... assignments // DSL-hack: just used for side effects on caller-side
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -561,7 +843,7 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
};
|
||||
}
|
||||
|
||||
private static HsHostingAssetEntity ipNumber(final Integer inet_addr_id) {
|
||||
private static HsHostingAssetRawEntity ipNumber(final Integer inet_addr_id) {
|
||||
return inet_addr_id != null ? hostingAssets.get(IP_NUMBER_ID_OFFSET + inet_addr_id) : null;
|
||||
}
|
||||
|
||||
@@ -569,7 +851,7 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
return hive_id != null ? hives.get(HIVE_ID_OFFSET + hive_id) : null;
|
||||
}
|
||||
|
||||
private static HsHostingAssetEntity pac(final Integer packet_id) {
|
||||
private static HsHostingAssetRawEntity pac(final Integer packet_id) {
|
||||
return packet_id != null ? hostingAssets.get(PACKET_ID_OFFSET + packet_id) : null;
|
||||
}
|
||||
|
||||
@@ -582,7 +864,11 @@ public class ImportHostingAssets extends ImportOfficeData {
|
||||
.filter(hae -> hae.getValue().getType() == t)
|
||||
.limit(maxCount)
|
||||
)
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, ImportHostingAssets::uniqueKeys, TreeMap::new)));
|
||||
}
|
||||
|
||||
protected static <V> V uniqueKeys(final V v1, final V v2) {
|
||||
throw new RuntimeException(String.format("Duplicate key for values %s and %s", v1, v2));
|
||||
}
|
||||
|
||||
private String firstOfEachType(
|
||||
|
@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -19,6 +20,7 @@ class PasswordPropertyUnitTest {
|
||||
private final ValidatableProperty<PasswordProperty, String> passwordProp =
|
||||
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(LINUX_SHA512).writeOnly();
|
||||
private final List<String> violations = new ArrayList<>();
|
||||
private EntityManager em = null; // not actually needed in these test cases
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
@@ -99,7 +101,12 @@ class PasswordPropertyUnitTest {
|
||||
void shouldComputeHash() {
|
||||
|
||||
// when
|
||||
final var result = passwordProp.compute(new PropertiesProvider() {
|
||||
final var result = passwordProp.compute(em, new PropertiesProvider() {
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> directProps() {
|
||||
|
12
src/test/resources/migration/hosting/emailalias.csv
Normal file
12
src/test/resources/migration/hosting/emailalias.csv
Normal file
@@ -0,0 +1,12 @@
|
||||
emailalias_id;pac_id;name;target
|
||||
2403;1094;lug00;michael.mellis@example.com
|
||||
2405;1094;lug00-wla-listar;|/home/pacs/lug00/users/in/mailinglist/listar
|
||||
2429;1112;mim00;mim12-mi@mim12.hostsharing.net
|
||||
2431;1112;mim00-abruf;michael.mellis@hostsharing.net
|
||||
2449;1112;mim00-hhfx;"mim00-hhfx,""|/usr/bin/formail -I 'Reply-To: hamburger-fx@example.net' | /usr/lib/sendmail mim00-hhfx-l"""
|
||||
2451;1112;mim00-hhfx-l;:include:/home/pacs/mim00/etc/hhfx.list
|
||||
2452;1112;mim00-empty;
|
||||
2453;1112;mim00-0_entries;""
|
||||
2454;1112;mim00-dev.null; /dev/null
|
||||
2455;1112;mim00-1_with_space;" ""|/home/pacs/mim00/install/corpslistar/listar"""
|
||||
2456;1112;mim00-1_with_single_quotes;'|/home/pacs/rir00/mailinglist/ecartis -r kybs06-intern'
|
|
19
src/test/resources/migration/hosting/unixuser.csv
Normal file
19
src/test/resources/migration/hosting/unixuser.csv
Normal file
@@ -0,0 +1,19 @@
|
||||
unixuser_id;name;comment;shell;homedir;locked;packet_id;userid;quota_softlimit;quota_hardlimit;storage_softlimit;storage_hardlimit
|
||||
100824;hsh00;Hostsharing Paket;/bin/bash;/home/pacs/hsh00;0;630;10000;0;0;0;0
|
||||
|
||||
5803;lug00;LUGs;/bin/bash;/home/pacs/lug00;0;1094;102090;0;0;0;0
|
||||
5805;lug00-wla.1;Paul Klemm;/bin/bash;/home/pacs/lug00/users/deaf;0;1094;102091;4;0;0;0
|
||||
5809;lug00-wla.2;Walter Müller;/bin/bash;/home/pacs/lug00/users/marl;0;1094;102093;4;8;0;0
|
||||
5811;lug00-ola.a;LUG OLA - POP a;/usr/bin/passwd;/home/pacs/lug00/users/marl.a;1;1094;102094;0;0;0;0
|
||||
5813;lug00-ola.b;LUG OLA - POP b;/usr/bin/passwd;/home/pacs/lug00/users/marl.b;1;1094;102095;0;0;0;0
|
||||
5835;lug00-test;Test;/usr/bin/passwd;/home/pacs/lug00/users/test;0;1094;102106;2000000;4000000;20;0
|
||||
|
||||
100705;hsh00-mim;Michael Mellis;/bin/false;/home/pacs/hsh00/users/mi;0;630;10003;0;0;0;0
|
||||
5964;mim00;Michael Mellis;/bin/bash;/home/pacs/mim00;0;1112;102147;0;0;0;0
|
||||
5966;mim00-1981;Jahrgangstreffen 1981;/bin/bash;/home/pacs/mim00/users/1981;0;1112;102148;128;256;0;0
|
||||
5990;mim00-mail;Mailbox;/bin/bash;/home/pacs/mim00/users/mail;0;1112;102160;0;0;0;0
|
||||
|
||||
167846;hsh00-dph;hsh00-uph;/bin/false;/home/pacs/hsh00/users/uph;0;630;110568;0;0;0;0
|
||||
169546;dph00;Reinhard Wiese;/bin/bash;/home/pacs/dph00;0;19959;110593;0;0;0;0
|
||||
169596;dph00-uph;Domain admin;/bin/bash;/home/pacs/dph00/users/uph;0;19959;110594;0;0;0;0
|
||||
|
|
Reference in New Issue
Block a user