allowSetupOfNonExistingSubdomainOfRegistrarLevelDomain

This commit is contained in:
Michael Hoennig 2024-09-06 13:08:02 +02:00
parent bab85c5581
commit 8316a88bce
4 changed files with 50 additions and 23 deletions

View File

@ -9,17 +9,23 @@ import javax.naming.NamingException;
import javax.naming.ServiceUnavailableException; import javax.naming.ServiceUnavailableException;
import javax.naming.directory.Attribute; import javax.naming.directory.Attribute;
import javax.naming.directory.InitialDirContext; import javax.naming.directory.InitialDirContext;
import java.util.HashMap;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List; import java.util.List;
import java.util.Map;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
public class Dns { public class Dns {
private static Result nextFakeResult = null; private final static Map<String, Result> fakeResults = new HashMap<>();
public static void fakeNextResult(final Result fakeResult) { public static void fakeResultForDomain(final String domainName, final Result fakeResult) {
nextFakeResult = fakeResult; fakeResults.put(domainName, fakeResult);
}
static void resetFakeResults() {
fakeResults.clear();
} }
public enum Status { public enum Status {
@ -56,12 +62,8 @@ public class Dns {
} }
public Result fetchRecordsOfType(final String recordType) { public Result fetchRecordsOfType(final String recordType) {
if (nextFakeResult != null) { if (fakeResults.containsKey(domainName)) {
try { return fakeResults.get(domainName);
return nextFakeResult;
} finally {
nextFakeResult = null;
}
} }
try { try {

View File

@ -60,12 +60,12 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
break; break;
case Dns.Status.INVALID_NAME: case Dns.Status.INVALID_NAME:
violations.add("Invalid domain name " + assetEntity.getIdentifier()); violations.add("[DNS] invalid domain name '" + assetEntity.getIdentifier() + "'");
break; break;
case Dns.Status.SERVICE_UNAVAILABLE: case Dns.Status.SERVICE_UNAVAILABLE:
case Dns.Status.UNKNOWN_FAILURE: case Dns.Status.UNKNOWN_FAILURE:
violations.add("DNS request for " + assetEntity.getIdentifier() + " failed: " + result.exception()); violations.add("[DNS] lookup failed for domain name '" + assetEntity.getIdentifier() + "': " + result.exception());
break; break;
} }

View File

@ -250,7 +250,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
void globalAdmin_canAddTopLevelAsset() { void globalAdmin_canAddTopLevelAsset() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
Dns.fakeNextResult(new Dns.Result(Dns.Status.NAME_NOT_FOUND, null, null)); Dns.fakeResultForDomain("example.com", new Dns.Result(Dns.Status.NAME_NOT_FOUND, null, null));
final var givenProject = realProjectRepo.findByCaption("D-1000111 default project").stream() final var givenProject = realProjectRepo.findByCaption("D-1000111 default project").stream()
.findAny().orElseThrow(); .findAny().orElseThrow();
final var bookingItem = givenSomeTemporaryBookingItem(() -> final var bookingItem = givenSomeTemporaryBookingItem(() ->

View File

@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRbacEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRbacEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import org.junit.jupiter.api.AfterEach;
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;
@ -39,6 +40,11 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
return validEntityBuilder("example.org"); return validEntityBuilder("example.org");
} }
@AfterEach
void cleanup() {
Dns.resetFakeResults();
}
enum InvalidDomainNameIdentifier { enum InvalidDomainNameIdentifier {
EMPTY(""), EMPTY(""),
TOO_LONG("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456890123456789.de"), TOO_LONG("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456890123456789.de"),
@ -57,7 +63,6 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
@EnumSource(InvalidDomainNameIdentifier.class) @EnumSource(InvalidDomainNameIdentifier.class)
void rejectsInvalidIdentifier(final InvalidDomainNameIdentifier testCase) { void rejectsInvalidIdentifier(final InvalidDomainNameIdentifier testCase) {
// given // given
Dns.fakeNextResult(new Dns.Result(Dns.Status.NAME_NOT_FOUND, null, null));
final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build(); final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build();
final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
@ -65,12 +70,11 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
final var result = validator.validateEntity(givenEntity); final var result = validator.validateEntity(givenEntity);
// then // then
assertThat(result).containsExactly( assertThat(result).contains(
"'identifier' expected to match 'example.org', but is '"+testCase.domainName+"'" "'identifier' expected to match 'example.org', but is '"+testCase.domainName+"'"
); );
} }
enum ValidDomainNameIdentifier { enum ValidDomainNameIdentifier {
SIMPLE("example.org"), SIMPLE("example.org"),
MAX_LENGTH("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234568901.de"), MAX_LENGTH("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234568901.de"),
@ -88,7 +92,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
@EnumSource(ValidDomainNameIdentifier.class) @EnumSource(ValidDomainNameIdentifier.class)
void acceptsValidIdentifier(final ValidDomainNameIdentifier testCase) { void acceptsValidIdentifier(final ValidDomainNameIdentifier testCase) {
// given // given
Dns.fakeNextResult(new Dns.Result(Dns.Status.NAME_NOT_FOUND, null, null)); Dns.fakeResultForDomain(testCase.domainName, new Dns.Result(Dns.Status.NAME_NOT_FOUND, null, null));
final var givenEntity = validEntityBuilder(testCase.domainName).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());
@ -111,7 +115,6 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
@Test @Test
void validatesReferencedEntities() { void validatesReferencedEntities() {
// given // given
Dns.fakeNextResult(new Dns.Result(Dns.Status.NAME_NOT_FOUND, null, null));
final var domainSetupHostingAssetEntity = validEntityBuilder() final var domainSetupHostingAssetEntity = validEntityBuilder()
.parentAsset(HsHostingAssetRealEntity.builder().type(CLOUD_SERVER).build()) .parentAsset(HsHostingAssetRealEntity.builder().type(CLOUD_SERVER).build())
.assignedToAsset(HsHostingAssetRealEntity.builder().type(MANAGED_SERVER).build()) .assignedToAsset(HsHostingAssetRealEntity.builder().type(MANAGED_SERVER).build())
@ -169,8 +172,10 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
void expectsEitherParentAssetOrBookingItem() { void expectsEitherParentAssetOrBookingItem() {
// given // given
Dns.fakeNextResult(new Dns.Result(Dns.Status.NAME_NOT_FOUND, null, null));
final var domainSetupHostingAssetEntity = validEntityBuilder().build(); final var domainSetupHostingAssetEntity = validEntityBuilder().build();
Dns.fakeResultForDomain(
domainSetupHostingAssetEntity.getIdentifier(),
new Dns.Result(Dns.Status.NAME_NOT_FOUND, null, null));
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType()); final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
// when // when
@ -183,16 +188,16 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
enum DnsLookupFailureTestCase { enum DnsLookupFailureTestCase {
SERVICE_UNAVAILABLE( SERVICE_UNAVAILABLE(
new ServiceUnavailableException("no Internet connection"), new ServiceUnavailableException("no Internet connection"),
"DNS request for example.org failed: javax.naming.ServiceUnavailableException: no Internet connection"), "[DNS] lookup failed for domain name 'example.org': javax.naming.ServiceUnavailableException: no Internet connection"),
NAME_NOT_FOUND( NAME_NOT_FOUND(
new NameNotFoundException("domain not registered"), new NameNotFoundException("domain name not found"),
null), // no null), // no
INVALID_NAME( INVALID_NAME(
new InvalidNameException("domain name too long or whatever"), new InvalidNameException("domain name too long or whatever"),
"Invalid domain name example.org"), "[DNS] invalid domain name 'example.org'"),
UNKNOWN_FAILURE( UNKNOWN_FAILURE(
new NamingException("some other problem"), new NamingException("some other problem"),
"DNS request for example.org failed: javax.naming.NamingException: some other problem"); "[DNS] lookup failed for domain name 'example.org': javax.naming.NamingException: some other problem");
public final NamingException givenException; public final NamingException givenException;
public final String expectedErrorMessage; public final String expectedErrorMessage;
@ -208,8 +213,10 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
void handlesDnsLookupFailures(final DnsLookupFailureTestCase testCase) { void handlesDnsLookupFailures(final DnsLookupFailureTestCase testCase) {
// given // given
Dns.fakeNextResult(Dns.Result.fromException(testCase.givenException));
final var domainSetupHostingAssetEntity = validEntityBuilder().build(); final var domainSetupHostingAssetEntity = validEntityBuilder().build();
Dns.fakeResultForDomain(
domainSetupHostingAssetEntity.getIdentifier(),
Dns.Result.fromException(testCase.givenException));
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType()); final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
// when // when
@ -223,6 +230,24 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
} }
} }
@Test
void allowSetupOfNonExistingSubdomainOfRegistrarLevelDomain() {
// given
final var domainSetupHostingAssetEntity = validEntityBuilder().build();
final var domainName = domainSetupHostingAssetEntity.getIdentifier();
Dns.fakeResultForDomain(
domainName,
Dns.Result.fromException(new NameNotFoundException("domain not registered")));
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
// then
assertThat(result).isEmpty();
}
private static HsHostingAssetRealEntity createValidParentDomainSetupAsset(final String parentDomainName) { private static HsHostingAssetRealEntity createValidParentDomainSetupAsset(final String parentDomainName) {
final var bookingItem = HsBookingItemRealEntity.builder() final var bookingItem = HsBookingItemRealEntity.builder()
.type(HsBookingItemType.DOMAIN_SETUP) .type(HsBookingItemType.DOMAIN_SETUP)