add-domain-setup-validation #71

Merged
hsh-michaelhoennig merged 13 commits from add-domain-setup-validation into master 2024-07-05 11:56:32 +02:00
5 changed files with 56 additions and 2 deletions
Showing only changes of commit 4c54abb742 - Show all commits

View File

@ -73,6 +73,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
final var entity = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); final var entity = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var mapped = new HsHostingAssetEntityProcessor(entity) final var mapped = new HsHostingAssetEntityProcessor(entity)
.preprocessEntity()
.validateEntity() .validateEntity()
.prepareForSave() .prepareForSave()
.saveUsing(assetRepo::save) .saveUsing(assetRepo::save)
@ -133,6 +134,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
new HsHostingAssetEntityPatcher(em, entity).apply(body); new HsHostingAssetEntityPatcher(em, entity).apply(body);
final var mapped = new HsHostingAssetEntityProcessor(entity) final var mapped = new HsHostingAssetEntityProcessor(entity)
.preprocessEntity()
.validateEntity() .validateEntity()
.prepareForSave() .prepareForSave()
.saveUsing(assetRepo::save) .saveUsing(assetRepo::save)

View File

@ -9,6 +9,7 @@ import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf; import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf;
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; 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.IntegerProperty.integerProperty;
@ -50,7 +51,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
booleanProperty("auto-WILDCARD-MX-RR").withDefault(true), booleanProperty("auto-WILDCARD-MX-RR").withDefault(true),
booleanProperty("auto-WILDCARD-A-RR").withDefault(true), booleanProperty("auto-WILDCARD-A-RR").withDefault(true),
booleanProperty("auto-WILDCARD-AAAA-RR").withDefault(true), booleanProperty("auto-WILDCARD-AAAA-RR").withDefault(true),
booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: @Peter booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: check, if that really works
booleanProperty("auto-WILDCARD-SPF-RR").withDefault(true), booleanProperty("auto-WILDCARD-SPF-RR").withDefault(true),
arrayOf( arrayOf(
stringProperty("user-RR").matchesRegEx(RR_REGEX_TTL_IN, RR_REGEX_IN_TTL).required() stringProperty("user-RR").matchesRegEx(RR_REGEX_TTL_IN, RR_REGEX_IN_TTL).required()
@ -59,10 +60,17 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
@Override @Override
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
// FIXME: should be auto-initialized
return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + "$"); return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + "$");
} }
@Override
public void preprocessEntity(final HsHostingAssetEntity entity) {
super.preprocessEntity(entity);
if (entity.getIdentifier() == null) {
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier()));
}
}
@Override @Override
@SneakyThrows @SneakyThrows
public List<String> validateContext(final HsHostingAssetEntity assetEntity) { public List<String> validateContext(final HsHostingAssetEntity assetEntity) {
@ -80,6 +88,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
} }
String toZonefileString(final HsHostingAssetEntity assetEntity) { String toZonefileString(final HsHostingAssetEntity assetEntity) {
// TODO.spec: we need to expand the templates (auto-...) in the same way as in Saltstack
return """ return """
$ORIGIN {domain}. $ORIGIN {domain}.
$TTL {ttl} $TTL {ttl}

View File

@ -14,6 +14,7 @@ import java.util.function.Function;
public class HsHostingAssetEntityProcessor { public class HsHostingAssetEntityProcessor {
private final HsEntityValidator<HsHostingAssetEntity> validator; private final HsEntityValidator<HsHostingAssetEntity> validator;
private String expectedStep = "preprocessEntity";
private HsHostingAssetEntity entity; private HsHostingAssetEntity entity;
private HsHostingAssetResource resource; private HsHostingAssetResource resource;
@ -22,8 +23,16 @@ public class HsHostingAssetEntityProcessor {
this.validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType()); this.validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType());
} }
/// initial step allowing to set default values before any validations
public HsHostingAssetEntityProcessor preprocessEntity() {
step("preprocessEntity", "validateEntity");
validator.preprocessEntity(entity);
return this;
}
/// validates the entity itself including its properties /// validates the entity itself including its properties
public HsHostingAssetEntityProcessor validateEntity() { public HsHostingAssetEntityProcessor validateEntity() {
step("validateEntity", "prepareForSave");
MultiValidationException.throwIfNotEmpty(validator.validateEntity(entity)); MultiValidationException.throwIfNotEmpty(validator.validateEntity(entity));
return this; return this;
} }
@ -31,17 +40,20 @@ public class HsHostingAssetEntityProcessor {
/// hashing passwords etc. /// hashing passwords etc.
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public HsHostingAssetEntityProcessor prepareForSave() { public HsHostingAssetEntityProcessor prepareForSave() {
step("prepareForSave", "saveUsing");
validator.prepareProperties(entity); validator.prepareProperties(entity);
return this; return this;
} }
public HsHostingAssetEntityProcessor saveUsing(final Function<HsHostingAssetEntity, HsHostingAssetEntity> saveFunction) { public HsHostingAssetEntityProcessor saveUsing(final Function<HsHostingAssetEntity, HsHostingAssetEntity> saveFunction) {
step("saveUsing", "validateContext");
entity = saveFunction.apply(entity); entity = saveFunction.apply(entity);
return this; return this;
} }
/// validates the entity within it's parent and child hierarchy (e.g. totals validators and other limits) /// validates the entity within it's parent and child hierarchy (e.g. totals validators and other limits)
public HsHostingAssetEntityProcessor validateContext() { public HsHostingAssetEntityProcessor validateContext() {
step("validateContext", "mapUsing");
MultiValidationException.throwIfNotEmpty(validator.validateContext(entity)); MultiValidationException.throwIfNotEmpty(validator.validateContext(entity));
return this; return this;
} }
@ -49,6 +61,7 @@ public class HsHostingAssetEntityProcessor {
/// maps entity to JSON resource representation /// maps entity to JSON resource representation
public HsHostingAssetEntityProcessor mapUsing( public HsHostingAssetEntityProcessor mapUsing(
final Function<HsHostingAssetEntity, HsHostingAssetResource> mapFunction) { final Function<HsHostingAssetEntity, HsHostingAssetResource> mapFunction) {
step("mapUsing", "revampProperties");
resource = mapFunction.apply(entity); resource = mapFunction.apply(entity);
return this; return this;
} }
@ -56,8 +69,18 @@ public class HsHostingAssetEntityProcessor {
/// removes write-only-properties and ads computed-properties /// removes write-only-properties and ads computed-properties
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public HsHostingAssetResource revampProperties() { public HsHostingAssetResource revampProperties() {
step("revampProperties", null);
final var revampedProps = validator.revampProperties(entity, (Map<String, Object>) resource.getConfig()); final var revampedProps = validator.revampProperties(entity, (Map<String, Object>) resource.getConfig());
resource.setConfig(revampedProps); resource.setConfig(revampedProps);
return resource; return resource;
} }
// Makes sure that the steps are called in the correct order.
// Could also be implemented using an interface per method, but that seems exaggerated.
private void step(final String current, final String next) {
if (!expectedStep.equals(current)) {
throw new IllegalStateException("expected " + expectedStep + " but got " + current);
}
expectedStep = next;
}
} }

View File

@ -49,6 +49,13 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
.collect(Collectors.toMap(p -> p.get("propertyName").toString(), p -> p)); .collect(Collectors.toMap(p -> p.get("propertyName").toString(), p -> p));
} }
/**
Gets called before any validations take place.
Allows to initialize fields and properties to default values.
*/
public void preprocessEntity(final E entity) {
}
protected ArrayList<String> validateProperties(final PropertiesProvider propsProvider) { protected ArrayList<String> validateProperties(final PropertiesProvider propsProvider) {
final var result = new ArrayList<String>(); final var result = new ArrayList<String>();

View File

@ -43,6 +43,19 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
)); ));
} }
@Test
void preprocessesTakesIdentifierFromParent() {
// given
final var givenEntity = validEntityBuilder().build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
validator.preprocessEntity(givenEntity);
// then
assertThat(givenEntity.getIdentifier()).isEqualTo(givenEntity.getParentAsset().getIdentifier());
}
@Test @Test
void rejectsInvalidIdentifier() { void rejectsInvalidIdentifier() {
// given // given