add DNS TXT record verification (WIP)
This commit is contained in:
parent
4a3af3f6fe
commit
e94f2f254a
@ -0,0 +1,74 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
|
import org.apache.commons.collections4.EnumerationUtils;
|
||||||
|
|
||||||
|
import javax.naming.InvalidNameException;
|
||||||
|
import javax.naming.NameNotFoundException;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.ServiceUnavailableException;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
public class Dns {
|
||||||
|
|
||||||
|
private static Result nextFakeResult = null;
|
||||||
|
|
||||||
|
public static void fakeNextResult(final Result fakeResult) {
|
||||||
|
nextFakeResult = fakeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
SUCCESS,
|
||||||
|
RECORD_TYPE_NOT_FOUND,
|
||||||
|
NAME_NOT_FOUND,
|
||||||
|
INVALID_NAME,
|
||||||
|
SERVICE_UNAVAILABLE,
|
||||||
|
UNKNOWN_FAILURE
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Result(Status status, List<String> records, NamingException exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String domainName;
|
||||||
|
|
||||||
|
public Dns(final String domainName) {
|
||||||
|
this.domainName = domainName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result fetchRecordsOfType(final String recordType) {
|
||||||
|
if (nextFakeResult != null) {
|
||||||
|
try {
|
||||||
|
return nextFakeResult;
|
||||||
|
} finally {
|
||||||
|
nextFakeResult = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final var env = new Hashtable<>();
|
||||||
|
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
|
||||||
|
final Attribute r = new InitialDirContext(env)
|
||||||
|
.getAttributes(domainName, new String[] { recordType })
|
||||||
|
.get(recordType);
|
||||||
|
return new Result(
|
||||||
|
r == null ? Status.RECORD_TYPE_NOT_FOUND : Status.SUCCESS,
|
||||||
|
r == null
|
||||||
|
? emptyList()
|
||||||
|
: EnumerationUtils.toList(r.getAll()).stream().map(Object::toString).toList(),
|
||||||
|
null);
|
||||||
|
} catch (final ServiceUnavailableException e) {
|
||||||
|
return new Result(Status.SERVICE_UNAVAILABLE, null, e);
|
||||||
|
} catch (final NameNotFoundException e) {
|
||||||
|
return new Result(Status.NAME_NOT_FOUND, null, e);
|
||||||
|
} catch (InvalidNameException e) {
|
||||||
|
return new Result(Status.INVALID_NAME, null, e);
|
||||||
|
} catch (NamingException e) {
|
||||||
|
return new Result(Status.UNKNOWN_FAILURE, null, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,8 @@ 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.ArrayList;
|
||||||
|
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;
|
||||||
@ -41,6 +43,40 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
|||||||
// return violations;
|
// return violations;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
||||||
|
|
||||||
|
final var violations = new ArrayList<String>();
|
||||||
|
final var result = new Dns(assetEntity.getIdentifier()).fetchRecordsOfType("TXT");
|
||||||
|
switch ( result.status() ) {
|
||||||
|
case Dns.Status.SUCCESS:
|
||||||
|
final var found = result.records().stream().filter(r -> r.contains("TXT Hostsharing-domain-setup-verification=FIXME")).findAny();
|
||||||
|
if (found.isPresent()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Dns.Status.RECORD_TYPE_NOT_FOUND:
|
||||||
|
violations.add("Domain " + assetEntity.getIdentifier() + " exists, but no record 'TXT Hostsharing-domain-setup-challenge:FIXME' found ");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Dns.Status.NAME_NOT_FOUND:
|
||||||
|
// no DNS verification necessary
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Dns.Status.INVALID_NAME:
|
||||||
|
violations.add("Invalid domain name " + assetEntity.getIdentifier());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Dns.Status.SERVICE_UNAVAILABLE:
|
||||||
|
case Dns.Status.UNKNOWN_FAILURE:
|
||||||
|
violations.add("DNS request for " + assetEntity.getIdentifier() + " failed: " + result.exception());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
violations.addAll(super.validateEntity(assetEntity));
|
||||||
|
return violations;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||||
if ( assetEntity.getBookingItem() != null ) {
|
if ( assetEntity.getBookingItem() != null ) {
|
||||||
|
@ -9,6 +9,7 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
|||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealRepository;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealRepository;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealRepository;
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealRepository;
|
||||||
|
import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns;
|
||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository;
|
||||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||||
@ -249,6 +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));
|
||||||
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(() ->
|
||||||
|
@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
|
|
||||||
class HsDomainSetupHostingAssetValidatorUnitTest {
|
class HsDomainSetupHostingAssetValidatorUnitTest {
|
||||||
|
|
||||||
|
|
||||||
static HsHostingAssetRbacEntity.HsHostingAssetRbacEntityBuilder<?, ?> validEntityBuilder(final String domainName) {
|
static HsHostingAssetRbacEntity.HsHostingAssetRbacEntityBuilder<?, ?> validEntityBuilder(final String domainName) {
|
||||||
final HsBookingItemRealEntity bookingItem = HsBookingItemRealEntity.builder()
|
final HsBookingItemRealEntity bookingItem = HsBookingItemRealEntity.builder()
|
||||||
.type(HsBookingItemType.DOMAIN_SETUP)
|
.type(HsBookingItemType.DOMAIN_SETUP)
|
||||||
@ -54,6 +53,7 @@ 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());
|
||||||
|
|
||||||
@ -84,6 +84,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));
|
||||||
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());
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ 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())
|
||||||
@ -161,7 +163,9 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
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();
|
||||||
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
|
|||||||
512167, // 11139, partner without contractual contact
|
512167, // 11139, partner without contractual contact
|
||||||
512170, // 11142, partner without contractual contact
|
512170, // 11142, partner without contractual contact
|
||||||
511725, // 10764, partner without contractual contact
|
511725, // 10764, partner without contractual contact
|
||||||
// 512171, // 11143, partner without partner contact -- exc
|
// 512171, // 11143, partner without partner contact -- exception
|
||||||
-1
|
-1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user