1
0

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:
Michael Hoennig
2024-08-01 13:12:58 +02:00
parent e1fda412ae
commit d6a0511d98
50 changed files with 1132 additions and 337 deletions

View File

@ -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 );
}

View File

@ -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)

View File

@ -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();
}
}

View File

@ -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()));
}

View File

@ -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() {

View File

@ -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);

View File

@ -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..> ");
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

View File

@ -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]$");
}
}

View File

@ -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());
}
}

View File

@ -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));

View File

@ -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));

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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("") +
"@" +

View File

@ -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\\._-]*$");
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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]$");
}
}

View File

@ -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()

View File

@ -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_]+$");
}

View File

@ -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(

View File

@ -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_]+$");
}

View File

@ -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_]+$");
}

View File

@ -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(

View File

@ -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_]+$");
}

View File

@ -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;
}
}

View File

@ -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`.

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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 (':')");

View File

@ -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);
}

View File

@ -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

View File

@ -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 + "\"";
}

View File

@ -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) {