check-domain-setup-permission #97
@ -13,6 +13,7 @@ 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 java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
@ -39,6 +40,14 @@ public class Dns {
|
|||||||
|
|
||||||
public record Result(Status status, List<String> records, NamingException exception) {
|
public record Result(Status status, List<String> records, NamingException exception) {
|
||||||
|
|
||||||
|
public static Optional<String> superDomain(final String domainName) {
|
||||||
|
final var parts = domainName.split("\\.", 2);
|
||||||
|
if (parts.length == 2) {
|
||||||
|
return Optional.of(parts[1]);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
public static Result fromRecords(final NamingEnumeration<?> recordEnumeration) {
|
public static Result fromRecords(final NamingEnumeration<?> recordEnumeration) {
|
||||||
final List<String> records = recordEnumeration == null
|
final List<String> records = recordEnumeration == null
|
||||||
? emptyList()
|
? emptyList()
|
||||||
@ -52,10 +61,10 @@ public class Dns {
|
|||||||
|
|
||||||
public static Result fromException(final NamingException exception) {
|
public static Result fromException(final NamingException exception) {
|
||||||
return switch (exception) {
|
return switch (exception) {
|
||||||
case ServiceUnavailableException exc -> new Result(Status.SERVICE_UNAVAILABLE, null, exc);
|
case ServiceUnavailableException exc -> new Result(Status.SERVICE_UNAVAILABLE, emptyList(), exc);
|
||||||
case NameNotFoundException exc -> new Result(Status.NAME_NOT_FOUND, null, exc);
|
case NameNotFoundException exc -> new Result(Status.NAME_NOT_FOUND, emptyList(), exc);
|
||||||
case InvalidNameException exc -> new Result(Status.INVALID_NAME, null, exc);
|
case InvalidNameException exc -> new Result(Status.INVALID_NAME, emptyList(), exc);
|
||||||
case NamingException exc -> new Result(Status.UNKNOWN_FAILURE, null, exc);
|
case NamingException exc -> new Result(Status.UNKNOWN_FAILURE, emptyList(), exc);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
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.Dns.Result.superDomain;
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainHttpSetupHostingAssetValidator.SUBDOMAIN_NAME_REGEX;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainHttpSetupHostingAssetValidator.SUBDOMAIN_NAME_REGEX;
|
||||||
|
|
||||||
class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
||||||
@ -15,45 +17,55 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
|||||||
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
|
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
|
||||||
|
|
||||||
HsDomainSetupHostingAssetValidator() {
|
HsDomainSetupHostingAssetValidator() {
|
||||||
super( DOMAIN_SETUP,
|
super(
|
||||||
|
DOMAIN_SETUP,
|
||||||
AlarmContact.isOptional(),
|
AlarmContact.isOptional(),
|
||||||
|
|
||||||
NO_EXTRA_PROPERTIES);
|
NO_EXTRA_PROPERTIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @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>();
|
// final var violations = new ArrayList<String>();
|
||||||
// if ( assetEntity.getBookingItem() != null ) {
|
// if ( assetEntity.getBookingItem() != null ) {
|
||||||
// final var bookingItemDomainName = assetEntity .getDirectValue(DOMAIN_NAME_PROPERTY_NAME, String.class);
|
// final var bookingItemDomainName = assetEntity .getDirectValue(DOMAIN_NAME_PROPERTY_NAME, String.class);
|
||||||
// if ( bookingItemDomainName ) {
|
// if ( bookingItemDomainName ) {
|
||||||
// violations.add("'" + bookingItem.toShortString() + ".resources." + DOMAIN_NAME_PROPERTY_NAME + "' = '" + domainName + "' is a forbidden Hostsharing domain name");
|
// violations.add("'" + bookingItem.toShortString() + ".resources." + DOMAIN_NAME_PROPERTY_NAME + "' = '" + domainName + "' is a forbidden Hostsharing domain name");
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// violations.addAll(super.validateEntity(assetEntity));
|
// violations.addAll(super.validateEntity(assetEntity));
|
||||||
// return violations;
|
// return violations;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
||||||
|
|
||||||
final var violations = new ArrayList<String>();
|
final var violations = new ArrayList<String>();
|
||||||
final var result = new Dns(assetEntity.getIdentifier()).fetchRecordsOfType("TXT");
|
final var domainName = assetEntity.getIdentifier();
|
||||||
switch ( result.status() ) {
|
final var dnsResult = new Dns(domainName).fetchRecordsOfType("TXT");
|
||||||
|
switch (dnsResult.status()) {
|
||||||
hsh-michaelhoennig marked this conversation as resolved
|
|||||||
case Dns.Status.SUCCESS:
|
case Dns.Status.SUCCESS:
|
||||||
final var code = assetEntity.getBookingItem().getDirectValue("verificationCode", String.class);
|
final var code = assetEntity.getBookingItem().getDirectValue("verificationCode", String.class);
|
||||||
final var found = result.records().stream().filter(r -> r.contains("Hostsharing-domain-setup-verification-code=" + code)).findAny();
|
final var expectedTxtRecordValue = "Hostsharing-domain-setup-verification-code=" + code;
|
||||||
|
final var found = findTxtRecord(dnsResult, expectedTxtRecordValue)
|
||||||
|
.or(() -> superDomain(domainName)
|
||||||
|
.flatMap(superDomainName -> findTxtRecord(
|
||||||
|
new Dns(superDomainName).fetchRecordsOfType("TXT"),
|
||||||
|
expectedTxtRecordValue))
|
||||||
|
);
|
||||||
if (found.isEmpty()) {
|
if (found.isEmpty()) {
|
||||||
violations.add("[DNS] no TXT record 'Hostsharing-domain-setup-verification="+code+"' with valid hash found for domain name '" + assetEntity.getIdentifier() + "'");
|
violations.add(
|
||||||
|
"[DNS] no TXT record 'Hostsharing-domain-setup-verification=" + code + "' found for domain name '"
|
||||||
|
+ assetEntity.getIdentifier() + "'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -65,22 +77,28 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
|||||||
violations.add("[DNS] 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] lookup failed for domain name '" + assetEntity.getIdentifier() + "': " + result.exception());
|
violations.add("[DNS] lookup failed for domain name '" + assetEntity.getIdentifier() + "': " + dnsResult.exception());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
hsh-marcsandlus
commented
findRecord findRecord
|
|||||||
|
|
||||||
violations.addAll(super.validateEntity(assetEntity));
|
violations.addAll(super.validateEntity(assetEntity));
|
||||||
return violations;
|
return violations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Optional<String> findTxtRecord(final Dns.Result result, final String expectedTxtRecordValue) {
|
||||||
|
return result.records().stream()
|
||||||
|
.filter(r -> r.contains(expectedTxtRecordValue))
|
||||||
|
.findAny();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||||
if ( assetEntity.getBookingItem() != null ) {
|
if (assetEntity.getBookingItem() != null) {
|
||||||
final var bookingItemDomainName = assetEntity.getBookingItem().getDirectValue(DOMAIN_NAME_PROPERTY_NAME, String.class);
|
final var bookingItemDomainName = assetEntity.getBookingItem()
|
||||||
return Pattern.compile(bookingItemDomainName, Pattern.CASE_INSENSITIVE|Pattern.LITERAL);
|
.getDirectValue(DOMAIN_NAME_PROPERTY_NAME, String.class);
|
||||||
|
return Pattern.compile(bookingItemDomainName, Pattern.CASE_INSENSITIVE | Pattern.LITERAL);
|
||||||
}
|
}
|
||||||
final var parentDomainName = assetEntity.getParentAsset().getIdentifier();
|
final var parentDomainName = assetEntity.getParentAsset().getIdentifier();
|
||||||
return Pattern.compile(SUBDOMAIN_NAME_REGEX + "\\." + parentDomainName.replace(".", "\\."), Pattern.CASE_INSENSITIVE);
|
return Pattern.compile(SUBDOMAIN_NAME_REGEX + "\\." + parentDomainName.replace(".", "\\."), Pattern.CASE_INSENSITIVE);
|
||||||
|
@ -76,7 +76,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).contains(
|
assertThat(result).contains(
|
||||||
"'identifier' expected to match 'example.org', but is '"+testCase.domainName+"'"
|
"'identifier' expected to match 'example.org', but is '" + testCase.domainName + "'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(strings = {"not-matching-booking-item-domain-name.org", "indirect.subdomain.example.org"})
|
@ValueSource(strings = { "not-matching-booking-item-domain-name.org", "indirect.subdomain.example.org" })
|
||||||
void rejectsDomainNameWhichIsNotADirectSubdomainOfParentAsset(final String newDomainName) {
|
void rejectsDomainNameWhichIsNotADirectSubdomainOfParentAsset(final String newDomainName) {
|
||||||
// given
|
// given
|
||||||
final var domainSetupHostingAssetEntity = validEntityBuilder()
|
final var domainSetupHostingAssetEntity = validEntityBuilder()
|
||||||
@ -236,7 +236,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void allowSetupOfNonExistingSubdomainOfRegistrarLevelDomain() {
|
void allowSetupOfAvailableRegistrableDomain() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
final var domainSetupHostingAssetEntity = validEntityBuilder().build();
|
final var domainSetupHostingAssetEntity = validEntityBuilder().build();
|
||||||
@ -254,12 +254,13 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rejectSetupOfExistingDomainWithInvalidDnsVerification() {
|
void rejectSetupOfExistingRegistrableDomainWithoutValidDnsVerification() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
final var domainSetupHostingAssetEntity = validEntityBuilder().build();
|
final var domainSetupHostingAssetEntity = validEntityBuilder().build();
|
||||||
final var domainName = domainSetupHostingAssetEntity.getIdentifier();
|
final var domainName = domainSetupHostingAssetEntity.getIdentifier();
|
||||||
final var expectedHash = domainSetupHostingAssetEntity.getBookingItem().getDirectValue("verificationCode", String.class);
|
final var expectedHash = domainSetupHostingAssetEntity.getBookingItem()
|
||||||
|
.getDirectValue("verificationCode", String.class);
|
||||||
Dns.fakeResultForDomain(
|
Dns.fakeResultForDomain(
|
||||||
domainName,
|
domainName,
|
||||||
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=SOME-DEFINITELY-WRONG-HASH"));
|
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=SOME-DEFINITELY-WRONG-HASH"));
|
||||||
@ -269,16 +270,18 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).contains("[DNS] no TXT record 'Hostsharing-domain-setup-verification=" + expectedHash + "' with valid hash found for domain name 'example.org'");
|
assertThat(result).contains("[DNS] no TXT record 'Hostsharing-domain-setup-verification=" + expectedHash
|
||||||
|
+ "' found for domain name 'example.org'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void allowSetupOfExistingDomainWithValidDnsVerification() {
|
void allowSetupOfExistingRegistrableDomainWithValidDnsVerification() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
final var domainSetupHostingAssetEntity = validEntityBuilder().build();
|
final var domainSetupHostingAssetEntity = validEntityBuilder().build();
|
||||||
final var domainName = domainSetupHostingAssetEntity.getIdentifier();
|
final var domainName = domainSetupHostingAssetEntity.getIdentifier();
|
||||||
final var expectedHash = domainSetupHostingAssetEntity.getBookingItem().getDirectValue("verificationCode", String.class);
|
final var expectedHash = domainSetupHostingAssetEntity.getBookingItem()
|
||||||
|
.getDirectValue("verificationCode", String.class);
|
||||||
Dns.fakeResultForDomain(
|
Dns.fakeResultForDomain(
|
||||||
domainName,
|
domainName,
|
||||||
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=" + expectedHash));
|
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=" + expectedHash));
|
||||||
@ -291,6 +294,71 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
assertThat(result).isEmpty();
|
assertThat(result).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allowSetupOfUnregisteredSubdomainWithValidDnsVerificationInSuperDomain() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
final var domainSetupHostingAssetEntity = validEntityBuilder("sub.example.org").build();
|
||||||
|
// ... the new subdomain is not yet registered:
|
||||||
|
Dns.fakeResultForDomain(
|
||||||
|
"sub.example.org",
|
||||||
|
Dns.Result.fromException(new NameNotFoundException("domain not registered")));
|
||||||
|
// ... and a valid verification-code in the super-domain:
|
||||||
|
final var expectedHash = domainSetupHostingAssetEntity.getBookingItem()
|
||||||
|
.getDirectValue("verificationCode", String.class);
|
||||||
|
Dns.fakeResultForDomain(
|
||||||
|
"example.org",
|
||||||
|
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=" + expectedHash));
|
||||||
|
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allowSetupOfExistingSubdomainWithValidDnsVerificationInSuperDomain() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
final var domainSetupHostingAssetEntity = validEntityBuilder("sub.example.org").build();
|
||||||
|
// ... the new subdomain is already registered:
|
||||||
|
Dns.fakeResultForDomain("sub.example.org", Dns.Result.fromRecords());
|
||||||
|
// ... and a valid verification-code in the super-domain:
|
||||||
|
final var expectedHash = domainSetupHostingAssetEntity.getBookingItem()
|
||||||
|
.getDirectValue("verificationCode", String.class);
|
||||||
|
Dns.fakeResultForDomain(
|
||||||
|
"example.org",
|
||||||
|
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=" + expectedHash));
|
||||||
|
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectSetupOfExistingSubdomainWithoutDnsVerification() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
final var domainSetupHostingAssetEntity = validEntityBuilder("sub.example.org").build();
|
||||||
|
// ... the new subdomain is already registered:
|
||||||
|
Dns.fakeResultForDomain("sub.example.org", Dns.Result.fromRecords());
|
||||||
|
final var expectedHash = domainSetupHostingAssetEntity.getBookingItem()
|
||||||
|
.getDirectValue("verificationCode", String.class);
|
||||||
|
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
||||||
|
|
||||||
|
// when
|
||||||
hsh-marcsandlus
commented
DomainSetup DomainSetup
|
|||||||
|
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).contains("[DNS] no TXT record 'Hostsharing-domain-setup-verification=" + expectedHash
|
||||||
|
+ "' found for domain name 'sub.example.org'");
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user
use getParentAsset == null + Test