Compare commits

..

2 Commits

Author SHA1 Message Date
Michael Hoennig
4a3af3f6fe amend test data+assertions to new validation rules 2024-09-05 13:51:08 +02:00
Michael Hoennig
0ed7264fc1 check hostingasset domain-setup domain name against booking item and parent asset 2024-09-05 13:22:20 +02:00
3 changed files with 98 additions and 26 deletions

View File

@ -2,42 +2,52 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainHttpSetupHostingAssetValidator.SUBDOMAIN_NAME_REGEX;
class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator { class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}"; public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}";
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
private final Pattern identifierPattern;
HsDomainSetupHostingAssetValidator() { HsDomainSetupHostingAssetValidator() {
super( DOMAIN_SETUP, super( DOMAIN_SETUP,
AlarmContact.isOptional(), AlarmContact.isOptional(),
NO_EXTRA_PROPERTIES); NO_EXTRA_PROPERTIES);
this.identifierPattern = Pattern.compile(FQDN_REGEX);
} }
@Override // @Override
public List<String> validateEntity(final HsHostingAsset assetEntity) { // public List<String> validateEntity(final HsHostingAsset assetEntity) {
// TODO.impl: for newly created entities, check the permission of setting up a domain // // TODO.impl: for newly created entities, check the permission of setting up a domain
// // //
// allow if // // allow if
// - user has Admin/Agent-role for all its sub-domains and the direct parent-Domain which are set up at at Hostsharing // // - user has Admin/Agent-role for all its sub-domains and the direct parent-Domain which are set up at at Hostsharing
// - domain has DNS zone with TXT record approval // // - domain has DNS zone with TXT record approval
// - parent-domain has DNS zone with TXT record approval // // - parent-domain has DNS zone with TXT record approval
// // //
// TXT-Record check: // // TXT-Record check:
// new InitialDirContext().getAttributes("dns:_netblocks.google.com", new String[] { "TXT"}).get("TXT").getAll(); // // new InitialDirContext().getAttributes("dns:_netblocks.google.com", new String[] { "TXT"}).get("TXT").getAll();
// final var violations = new ArrayList<String>();
return super.validateEntity(assetEntity); // if ( assetEntity.getBookingItem() != null ) {
} // final var bookingItemDomainName = assetEntity .getDirectValue(DOMAIN_NAME_PROPERTY_NAME, String.class);
// if ( bookingItemDomainName ) {
// violations.add("'" + bookingItem.toShortString() + ".resources." + DOMAIN_NAME_PROPERTY_NAME + "' = '" + domainName + "' is a forbidden Hostsharing domain name");
// }
// }
// violations.addAll(super.validateEntity(assetEntity));
// return violations;
// }
@Override @Override
protected Pattern identifierPattern(final HsHostingAsset assetEntity) { protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
return identifierPattern; if ( assetEntity.getBookingItem() != null ) {
final var bookingItemDomainName = assetEntity.getBookingItem().getDirectValue(DOMAIN_NAME_PROPERTY_NAME, String.class);
return Pattern.compile(bookingItemDomainName, Pattern.CASE_INSENSITIVE|Pattern.LITERAL);
}
final var parentDomainName = assetEntity.getParentAsset().getIdentifier();
return Pattern.compile(SUBDOMAIN_NAME_REGEX + "\\." + parentDomainName.replace(".", "\\."), Pattern.CASE_INSENSITIVE);
} }
} }

View File

@ -257,7 +257,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.type(HsBookingItemType.DOMAIN_SETUP) .type(HsBookingItemType.DOMAIN_SETUP)
.caption("some temp domain setup booking item") .caption("some temp domain setup booking item")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("domainName", "example.org"))) entry("domainName", "example.com")))
.build() .build()
); );

View File

@ -7,6 +7,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.Map; import java.util.Map;
@ -17,11 +18,22 @@ import static org.assertj.core.api.Assertions.assertThat;
class HsDomainSetupHostingAssetValidatorUnitTest { class HsDomainSetupHostingAssetValidatorUnitTest {
static HsHostingAssetRbacEntity.HsHostingAssetRbacEntityBuilder<?, ?> validEntityBuilder() {
static HsHostingAssetRbacEntity.HsHostingAssetRbacEntityBuilder<?, ?> validEntityBuilder(final String domainName) {
final HsBookingItemRealEntity bookingItem = HsBookingItemRealEntity.builder()
.type(HsBookingItemType.DOMAIN_SETUP)
.resources(Map.ofEntries(
Map.entry("domainName", domainName)
))
.build();
return HsHostingAssetRbacEntity.builder() return HsHostingAssetRbacEntity.builder()
.type(DOMAIN_SETUP) .type(DOMAIN_SETUP)
.bookingItem(HsBookingItemRealEntity.builder().type(HsBookingItemType.DOMAIN_SETUP).build()) .bookingItem(bookingItem)
.identifier("example.org"); .identifier(domainName);
}
static HsHostingAssetRbacEntity.HsHostingAssetRbacEntityBuilder<?, ?> validEntityBuilder() {
return validEntityBuilder("example.org");
} }
enum InvalidDomainNameIdentifier { enum InvalidDomainNameIdentifier {
@ -50,13 +62,13 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactly( assertThat(result).containsExactly(
"'identifier' expected to match '^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}', but is '"+testCase.domainName+"'" "'identifier' expected to match 'example.org', but is '"+testCase.domainName+"'"
); );
} }
enum ValidDomainNameIdentifier { enum ValidDomainNameIdentifier {
SIMPLE("exampe.org"), SIMPLE("example.org"),
MAX_LENGTH("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234568901.de"), MAX_LENGTH("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234568901.de"),
WITH_DASH("example-test.com"), WITH_DASH("example-test.com"),
SUBDOMAIN("test.example.com"); SUBDOMAIN("test.example.com");
@ -72,7 +84,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
@EnumSource(ValidDomainNameIdentifier.class) @EnumSource(ValidDomainNameIdentifier.class)
void acceptsValidIdentifier(final ValidDomainNameIdentifier testCase) { void acceptsValidIdentifier(final ValidDomainNameIdentifier testCase) {
// given // given
final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build(); final var givenEntity = validEntityBuilder(testCase.domainName).identifier(testCase.domainName).build();
final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when // when
@ -111,6 +123,42 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
"'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is of type MANAGED_SERVER"); "'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is of type MANAGED_SERVER");
} }
@Test
void rejectsDomainNameNotMatchingBookingItemDomainName() {
// given
final var domainSetupHostingAssetEntity = validEntityBuilder()
.identifier("not-matching-booking-item-domain-name.org")
.build();
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'identifier' expected to match 'example.org', but is 'not-matching-booking-item-domain-name.org'");
}
@ParameterizedTest
@ValueSource(strings = {"not-matching-booking-item-domain-name.org", "indirect.subdomain.example.org"})
void rejectsDomainNameWhichIsNotADirectSubdomainOfParentAsset(final String newDomainName) {
// given
final var domainSetupHostingAssetEntity = validEntityBuilder()
.bookingItem(null)
.parentAsset(createValidParentDomainSetupAsset("example.org"))
.identifier(newDomainName)
.build();
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'identifier' expected to match '(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))\\.example\\.org', " +
"but is '" + newDomainName + "'");
}
@Test @Test
void expectsEitherParentAssetOrBookingItem() { void expectsEitherParentAssetOrBookingItem() {
// given // given
@ -123,4 +171,18 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).isEmpty(); assertThat(result).isEmpty();
} }
private static HsHostingAssetRealEntity createValidParentDomainSetupAsset(final String parentDomainName) {
final var bookingItem = HsBookingItemRealEntity.builder()
.type(HsBookingItemType.DOMAIN_SETUP)
.resources(Map.ofEntries(
Map.entry("domainName", parentDomainName)
))
.build();
final var parentAsset = HsHostingAssetRealEntity.builder()
.type(DOMAIN_SETUP)
.bookingItem(bookingItem)
.identifier(parentDomainName).build();
return parentAsset;
}
} }