check-domain-setup-permission (#97)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #97 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
parent
8e02610679
commit
a7d586f0f7
@ -1,10 +1,59 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
|
||||||
|
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns.REGISTRAR_LEVEL_DOMAINS;
|
||||||
|
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
|
||||||
|
|
||||||
class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator {
|
class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator {
|
||||||
|
|
||||||
|
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";
|
||||||
|
public static final String VERIFICATION_CODE_PROPERTY_NAME = "verificationCode";
|
||||||
|
|
||||||
HsDomainSetupBookingItemValidator() {
|
HsDomainSetupBookingItemValidator() {
|
||||||
super(
|
super(
|
||||||
// no properties yet. maybe later, the setup code goes here?
|
stringProperty(DOMAIN_NAME_PROPERTY_NAME).writeOnce()
|
||||||
|
.maxLength(253)
|
||||||
|
.matchesRegEx(FQDN_REGEX).describedAs("is not a (non-top-level) fully qualified domain name")
|
||||||
|
.notMatchesRegEx(REGISTRAR_LEVEL_DOMAINS).describedAs("is a forbidden registrar-level domain name")
|
||||||
|
.required(),
|
||||||
|
stringProperty(VERIFICATION_CODE_PROPERTY_NAME)
|
||||||
|
.readOnly().initializedBy(HsDomainSetupBookingItemValidator::generateVerificationCode)
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> validateEntity(final HsBookingItem bookingItem) {
|
||||||
|
final var violations = new ArrayList<String>();
|
||||||
|
final var domainName = bookingItem.getDirectValue(DOMAIN_NAME_PROPERTY_NAME, String.class);
|
||||||
|
if (!bookingItem.isLoaded() &&
|
||||||
|
domainName.matches("hostsharing.(com|net|org|coop|de)")) {
|
||||||
|
violations.add("'" + bookingItem.toShortString() + ".resources." + DOMAIN_NAME_PROPERTY_NAME + "' = '" + domainName
|
||||||
|
+ "' is a forbidden Hostsharing domain name");
|
||||||
|
}
|
||||||
|
violations.addAll(super.validateEntity(bookingItem));
|
||||||
|
return violations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateVerificationCode(final EntityManager em, final PropertiesProvider propertiesProvider) {
|
||||||
|
final var alphaNumeric = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||||
|
final var secureRandom = new SecureRandom();
|
||||||
|
final var sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < 40; ++i) {
|
||||||
|
if ( i > 0 && i % 4 == 0 ) {
|
||||||
|
sb.append("-");
|
||||||
|
}
|
||||||
|
sb.append(alphaNumeric.charAt(secureRandom.nextInt(alphaNumeric.length())));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.mapper.Array;
|
||||||
|
import org.apache.commons.collections4.EnumerationUtils;
|
||||||
|
|
||||||
|
import javax.naming.InvalidNameException;
|
||||||
|
import javax.naming.NameNotFoundException;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.ServiceUnavailableException;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static java.util.Arrays.stream;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
public class Dns {
|
||||||
|
|
||||||
|
public static final String[] REGISTRAR_LEVEL_DOMAINS = Array.of(
|
||||||
|
"[^.]+", // top-level-domains
|
||||||
|
"(co|org|gov|ac|sch)\\.uk",
|
||||||
|
"(com|net|org|edu|gov|asn|id)\\.au",
|
||||||
|
"(co|ne|or|ac|go)\\.jp",
|
||||||
|
"(com|net|org|gov|edu|ac)\\.cn",
|
||||||
|
"(com|net|org|gov|edu|mil|art)\\.br",
|
||||||
|
"(co|net|org|gen|firm|ind)\\.in",
|
||||||
|
"(com|net|org|gob|edu)\\.mx",
|
||||||
|
"(gov|edu)\\.it",
|
||||||
|
"(co|net|org|govt|ac|school|geek|kiwi)\\.nz",
|
||||||
|
"(co|ne|or|go|re|pe)\\.kr"
|
||||||
|
);
|
||||||
|
public static final Pattern[] REGISTRAR_LEVEL_DOMAIN_PATTERN = stream(REGISTRAR_LEVEL_DOMAINS)
|
||||||
|
.map(Pattern::compile)
|
||||||
|
.toArray(Pattern[]::new);
|
||||||
|
|
||||||
|
private final static Map<String, Result> fakeResults = new HashMap<>();
|
||||||
|
|
||||||
|
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 boolean isRegistrarLevelDomain(final String domainName) {
|
||||||
|
return stream(REGISTRAR_LEVEL_DOMAIN_PATTERN)
|
||||||
|
.anyMatch(p -> p.matcher(domainName).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param domainName a fully qualified domain name
|
||||||
|
* @return true if `domainName` can be registered at a registrar, false if it's a subdomain of such or a registrar-level domain itself
|
||||||
|
*/
|
||||||
|
public static boolean isRegistrableDomain(final String domainName) {
|
||||||
|
return !isRegistrarLevelDomain(domainName) &&
|
||||||
|
superDomain(domainName).map(Dns::isRegistrarLevelDomain).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fakeResultForDomain(final String domainName, final Result fakeResult) {
|
||||||
|
fakeResults.put(domainName, fakeResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetFakeResults() {
|
||||||
|
fakeResults.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
SUCCESS,
|
||||||
|
NAME_NOT_FOUND,
|
||||||
|
INVALID_NAME,
|
||||||
|
SERVICE_UNAVAILABLE,
|
||||||
|
UNKNOWN_FAILURE
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Result(Status status, List<String> records, NamingException exception) {
|
||||||
|
|
||||||
|
|
||||||
|
public static Result fromRecords(final NamingEnumeration<?> recordEnumeration) {
|
||||||
|
final List<String> records = recordEnumeration == null
|
||||||
|
? emptyList()
|
||||||
|
: EnumerationUtils.toList(recordEnumeration).stream().map(Object::toString).toList();
|
||||||
|
return new Result(Status.SUCCESS, records, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result fromRecords(final String... records) {
|
||||||
|
return new Result(Status.SUCCESS, stream(records).toList(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result fromException(final NamingException exception) {
|
||||||
|
return switch (exception) {
|
||||||
|
case ServiceUnavailableException exc -> new Result(Status.SERVICE_UNAVAILABLE, emptyList(), exc);
|
||||||
|
case NameNotFoundException exc -> new Result(Status.NAME_NOT_FOUND, emptyList(), exc);
|
||||||
|
case InvalidNameException exc -> new Result(Status.INVALID_NAME, emptyList(), exc);
|
||||||
|
case NamingException exc -> new Result(Status.UNKNOWN_FAILURE, emptyList(), exc);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String domainName;
|
||||||
|
|
||||||
|
public Dns(final String domainName) {
|
||||||
|
this.domainName = domainName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result fetchRecordsOfType(final String recordType) {
|
||||||
|
if (fakeResults.containsKey(domainName)) {
|
||||||
|
return fakeResults.get(domainName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final var env = new Hashtable<>();
|
||||||
|
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
|
||||||
|
final Attribute records = new InitialDirContext(env)
|
||||||
|
.getAttributes(domainName, new String[] { recordType })
|
||||||
|
.get(recordType);
|
||||||
|
return Result.fromRecords(records != null ? records.getAll() : null);
|
||||||
|
} catch (final NamingException exception) {
|
||||||
|
return Result.fromException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
final var result = new Dns("example.org").fetchRecordsOfType("TXT");
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,55 +3,104 @@ 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.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
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.superDomain;
|
||||||
|
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
|
final var violations = // new ArrayList<String>();
|
||||||
//
|
super.validateEntity(assetEntity);
|
||||||
// reject, if the domain is any of these:
|
if (!violations.isEmpty()) {
|
||||||
// hostsharing.com|net|org|coop, // just to be on the safe side
|
return violations;
|
||||||
// [^.}+, // top-level-domain
|
}
|
||||||
// co.uk, org.uk, gov.uk, ac.uk, sch.uk,
|
|
||||||
// com.au, net.au, org.au, edu.au, gov.au, asn.au, id.au,
|
|
||||||
// co.jp, ne.jp, or.jp, ac.jp, go.jp,
|
|
||||||
// com.cn, net.cn, org.cn, gov.cn, edu.cn, ac.cn,
|
|
||||||
// com.br, net.br, org.br, gov.br, edu.br, mil.br, art.br,
|
|
||||||
// co.in, net.in, org.in, gen.in, firm.in, ind.in,
|
|
||||||
// com.mx, net.mx, org.mx, gob.mx, edu.mx,
|
|
||||||
// gov.it, edu.it,
|
|
||||||
// co.nz, net.nz, org.nz, govt.nz, ac.nz, school.nz, geek.nz, kiwi.nz,
|
|
||||||
// co.kr, ne.kr, or.kr, go.kr, re.kr, pe.kr
|
|
||||||
//
|
|
||||||
// allow if
|
|
||||||
// - 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
|
|
||||||
// - parent-domain has DNS zone with TXT record approval
|
|
||||||
//
|
|
||||||
// TXT-Record check:
|
|
||||||
// new InitialDirContext().getAttributes("dns:_netblocks.google.com", new String[] { "TXT"}).get("TXT").getAll();
|
|
||||||
|
|
||||||
return super.validateEntity(assetEntity);
|
final var domainName = assetEntity.getIdentifier();
|
||||||
|
final var dnsResult = new Dns(domainName).fetchRecordsOfType("TXT");
|
||||||
|
final Supplier<String> getCode = () -> assetEntity.getBookingItem().getDirectValue("verificationCode", String.class);
|
||||||
|
switch (dnsResult.status()) {
|
||||||
|
case Dns.Status.SUCCESS: {
|
||||||
|
final var expectedTxtRecordValue = "Hostsharing-domain-setup-verification-code=" + getCode.get();
|
||||||
|
final var verificationFound = findTxtRecord(dnsResult, expectedTxtRecordValue)
|
||||||
|
.or(() -> superDomain(domainName)
|
||||||
|
.flatMap(superDomainName -> findTxtRecord(
|
||||||
|
new Dns(superDomainName).fetchRecordsOfType("TXT"),
|
||||||
|
expectedTxtRecordValue))
|
||||||
|
);
|
||||||
|
if (verificationFound.isEmpty()) {
|
||||||
|
violations.add(
|
||||||
|
"[DNS] no TXT record '" + expectedTxtRecordValue +
|
||||||
|
"' found for domain name '" + domainName + "' (nor in its super-domain)");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Dns.Status.NAME_NOT_FOUND: {
|
||||||
|
if (isDnsVerificationRequiredForUnregisteredDomain(assetEntity)) {
|
||||||
|
final var superDomain = superDomain(domainName);
|
||||||
|
final var expectedTxtRecordValue = "Hostsharing-domain-setup-verification-code=" + getCode.get();
|
||||||
|
final var verificationFoundInSuperDomain = superDomain.flatMap(superDomainName -> findTxtRecord(
|
||||||
|
new Dns(superDomainName).fetchRecordsOfType("TXT"),
|
||||||
|
expectedTxtRecordValue));
|
||||||
|
if (verificationFoundInSuperDomain.isEmpty()) {
|
||||||
|
violations.add(
|
||||||
|
"[DNS] no TXT record '" + expectedTxtRecordValue +
|
||||||
|
"' found for domain name '" + superDomain.orElseThrow() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// otherwise no DNS verification to be able to setup DNS for domains to register
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Dns.Status.INVALID_NAME:
|
||||||
|
violations.add("[DNS] invalid domain name '" + assetEntity.getIdentifier() + "'");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Dns.Status.SERVICE_UNAVAILABLE:
|
||||||
|
case Dns.Status.UNKNOWN_FAILURE:
|
||||||
|
violations.add("[DNS] lookup failed for domain name '" + assetEntity.getIdentifier() + "': " + dnsResult.exception());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDnsVerificationRequiredForUnregisteredDomain(final HsHostingAsset assetEntity) {
|
||||||
|
return !Dns.isRegistrableDomain(assetEntity.getIdentifier())
|
||||||
|
&& assetEntity.getParentAsset() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Optional<String> findTxtRecord(final Dns.Result result, final String expectedTxtRecordValue) {
|
||||||
|
return result.records().stream()
|
||||||
|
.filter(r -> r.contains(expectedTxtRecordValue))
|
||||||
|
.findAny();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package net.hostsharing.hsadminng.hs.validation;
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.hostsharing.hsadminng.mapper.Array;
|
import net.hostsharing.hsadminng.mapper.Array;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -15,11 +17,19 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
|
|||||||
|
|
||||||
protected static final String[] KEY_ORDER = Array.join(
|
protected static final String[] KEY_ORDER = Array.join(
|
||||||
ValidatableProperty.KEY_ORDER_HEAD,
|
ValidatableProperty.KEY_ORDER_HEAD,
|
||||||
Array.of("matchesRegEx", "minLength", "maxLength", "provided"),
|
Array.of("matchesRegEx", "matchesRegExDescription",
|
||||||
|
"notMatchesRegEx", "notMatchesRegExDescription",
|
||||||
|
"minLength", "maxLength",
|
||||||
|
"provided"),
|
||||||
ValidatableProperty.KEY_ORDER_TAIL,
|
ValidatableProperty.KEY_ORDER_TAIL,
|
||||||
Array.of("undisclosed"));
|
Array.of("undisclosed"));
|
||||||
private String[] provided;
|
private String[] provided;
|
||||||
private Pattern[] matchesRegEx;
|
private Pattern[] matchesRegEx;
|
||||||
|
private String matchesRegExDescription;
|
||||||
|
private Pattern[] notMatchesRegEx;
|
||||||
|
private String notMatchesRegExDescription;
|
||||||
|
@Setter(AccessLevel.PRIVATE)
|
||||||
|
private Consumer<String> describedAsConsumer;
|
||||||
private Integer minLength;
|
private Integer minLength;
|
||||||
private Integer maxLength;
|
private Integer maxLength;
|
||||||
private boolean undisclosed;
|
private boolean undisclosed;
|
||||||
@ -56,10 +66,23 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
|
|||||||
|
|
||||||
public P matchesRegEx(final String... regExPattern) {
|
public P matchesRegEx(final String... regExPattern) {
|
||||||
this.matchesRegEx = stream(regExPattern).map(Pattern::compile).toArray(Pattern[]::new);
|
this.matchesRegEx = stream(regExPattern).map(Pattern::compile).toArray(Pattern[]::new);
|
||||||
|
this.describedAsConsumer = violationMessage -> matchesRegExDescription = violationMessage;
|
||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// predifined values, similar to fixed values in a combobox
|
public P notMatchesRegEx(final String... regExPattern) {
|
||||||
|
this.notMatchesRegEx = stream(regExPattern).map(Pattern::compile).toArray(Pattern[]::new);
|
||||||
|
this.describedAsConsumer = violationMessage -> notMatchesRegExDescription = violationMessage;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public P describedAs(final String violationMessage) {
|
||||||
|
describedAsConsumer.accept(violationMessage);
|
||||||
|
describedAsConsumer = null;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// predefined values, similar to fixed values in a combobox
|
||||||
public P provided(final String... provided) {
|
public P provided(final String... provided) {
|
||||||
this.provided = provided;
|
this.provided = provided;
|
||||||
return self();
|
return self();
|
||||||
@ -78,16 +101,10 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
|
|||||||
@Override
|
@Override
|
||||||
protected void validate(final List<String> result, final String propValue, final PropertiesProvider propProvider) {
|
protected void validate(final List<String> result, final String propValue, final PropertiesProvider propProvider) {
|
||||||
super.validate(result, propValue, propProvider);
|
super.validate(result, propValue, propProvider);
|
||||||
if (minLength != null && propValue.length()<minLength) {
|
validateMinLength(result, propValue);
|
||||||
result.add(propertyName + "' length is expected to be at min " + minLength + " but length of " + display(propValue) + " is " + propValue.length());
|
validateMaxLength(result, propValue);
|
||||||
}
|
validateMatchesRegEx(result, propValue);
|
||||||
if (maxLength != null && propValue.length()>maxLength) {
|
validateNotMatchesRegEx(result, propValue);
|
||||||
result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of " + display(propValue) + " is " + propValue.length());
|
|
||||||
}
|
|
||||||
if (matchesRegEx != null &&
|
|
||||||
stream(matchesRegEx).map(p -> p.matcher(propValue)).noneMatch(Matcher::matches)) {
|
|
||||||
result.add(propertyName + "' is expected to match any of " + Arrays.toString(matchesRegEx) + " but " + display(propValue) + " does not match" + (matchesRegEx.length>1?" any":""));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -99,4 +116,47 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
|
|||||||
protected String simpleTypeName() {
|
protected String simpleTypeName() {
|
||||||
return "string";
|
return "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateMinLength(final List<String> result, final String propValue) {
|
||||||
|
if (minLength != null && propValue.length()<minLength) {
|
||||||
|
result.add(propertyName + "' length is expected to be at min " + minLength + " but length of " + display(propValue) + " is " + propValue.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateMaxLength(final List<String> result, final String propValue) {
|
||||||
|
if (maxLength != null && propValue.length()>maxLength) {
|
||||||
|
result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of " + display(propValue) + " is " + propValue.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateMatchesRegEx(final List<String> result, final String propValue) {
|
||||||
|
if (matchesRegEx != null &&
|
||||||
|
stream(matchesRegEx).map(p -> p.matcher(propValue)).noneMatch(Matcher::matches)) {
|
||||||
|
if (matchesRegExDescription != null) {
|
||||||
|
result.add(propertyName + "' = " + display(propValue) + " " + matchesRegExDescription);
|
||||||
|
} else if (matchesRegEx.length>1) {
|
||||||
|
result.add(propertyName + "' is expected to match any of " + Arrays.toString(matchesRegEx) +
|
||||||
|
" but " + display(propValue) + " does not match any");
|
||||||
|
} else {
|
||||||
|
result.add(propertyName + "' is expected to match " + Arrays.toString(matchesRegEx) + " but " + display(
|
||||||
|
propValue) +
|
||||||
|
" does not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateNotMatchesRegEx(final List<String> result, final String propValue) {
|
||||||
|
if (notMatchesRegEx != null &&
|
||||||
|
stream(notMatchesRegEx).map(p -> p.matcher(propValue)).anyMatch(Matcher::matches)) {
|
||||||
|
if (notMatchesRegExDescription != null) {
|
||||||
|
result.add(propertyName + "' = " + display(propValue) + " " + notMatchesRegExDescription);
|
||||||
|
} else if (notMatchesRegEx.length>1) {
|
||||||
|
result.add(propertyName + "' is expected not to match any of " + Arrays.toString(notMatchesRegEx) +
|
||||||
|
" but " + display(propValue) + " does match at least one");
|
||||||
|
} else {
|
||||||
|
result.add(propertyName + "' is expected not to match " + Arrays.toString(notMatchesRegEx) +
|
||||||
|
" but " + display(propValue) + " does match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.util.Map.entry;
|
||||||
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.DOMAIN_SETUP;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.right;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class HsDomainSetupBookingItemValidatorUnitTest {
|
||||||
|
|
||||||
|
public static final String TOO_LONG_DOMAIN_NAME = "asdfghijklmnopqrstuvwxyz0123456789.".repeat(8) + "example.org";
|
||||||
|
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||||
|
.debitorNumber(12345)
|
||||||
|
.build();
|
||||||
|
final HsBookingProjectRealEntity project = HsBookingProjectRealEntity.builder()
|
||||||
|
.debitor(debitor)
|
||||||
|
.caption("Test-Project")
|
||||||
|
.build();
|
||||||
|
private EntityManager em;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void acceptsRegisterableDomain() {
|
||||||
|
// given
|
||||||
|
final var domainSetupBookingItemEntity = HsBookingItemRealEntity.builder()
|
||||||
|
.type(DOMAIN_SETUP)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test-Domain")
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("domainName", "example.org")
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = HsBookingItemEntityValidatorRegistry.doValidate(em, domainSetupBookingItemEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void acceptsMaximumDomainNameLength() {
|
||||||
|
final var domainSetupBookingItemEntity = HsBookingItemRealEntity.builder()
|
||||||
|
.type(DOMAIN_SETUP)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test-Domain")
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("domainName", right(TOO_LONG_DOMAIN_NAME, 253))
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = HsBookingItemEntityValidatorRegistry.doValidate(em, domainSetupBookingItemEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsTooLongTotalName() {
|
||||||
|
final var domainSetupBookingItemEntity = HsBookingItemRealEntity.builder()
|
||||||
|
.type(DOMAIN_SETUP)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test-Domain")
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("domainName", right(TOO_LONG_DOMAIN_NAME, 254))
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = HsBookingItemEntityValidatorRegistry.doValidate(em, domainSetupBookingItemEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).contains("'D-12345:Test-Project:Test-Domain.resources.domainName' length is expected to be at max 253 but length of 'dfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.example.org' is 254");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {
|
||||||
|
"de", "com", "net", "org", "actually-any-top-level-domain",
|
||||||
|
"co.uk", "org.uk", "gov.uk", "ac.uk", "sch.uk",
|
||||||
|
"com.au", "net.au", "org.au", "edu.au", "gov.au", "asn.au", "id.au",
|
||||||
|
"co.jp", "ne.jp", "or.jp", "ac.jp", "go.jp",
|
||||||
|
"com.cn", "net.cn", "org.cn", "gov.cn", "edu.cn", "ac.cn",
|
||||||
|
"com.br", "net.br", "org.br", "gov.br", "edu.br", "mil.br", "art.br",
|
||||||
|
"co.in", "net.in", "org.in", "gen.in", "firm.in", "ind.in",
|
||||||
|
"com.mx", "net.mx", "org.mx", "gob.mx", "edu.mx",
|
||||||
|
"gov.it", "edu.it",
|
||||||
|
"co.nz", "net.nz", "org.nz", "govt.nz", "ac.nz", "school.nz", "geek.nz", "kiwi.nz",
|
||||||
|
"co.kr", "ne.kr", "or.kr", "go.kr", "re.kr", "pe.kr"
|
||||||
|
})
|
||||||
|
void rejectRegistrarLevelDomain(final String secondLevelRegistrarDomain) {
|
||||||
|
// given
|
||||||
|
final var domainSetupBookingItemEntity = HsBookingItemRealEntity.builder()
|
||||||
|
.type(DOMAIN_SETUP)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test-Domain")
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("domainName", secondLevelRegistrarDomain)
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = HsBookingItemEntityValidatorRegistry.doValidate(em, domainSetupBookingItemEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).contains(
|
||||||
|
"'D-12345:Test-Project:Test-Domain.resources.domainName' = '" +
|
||||||
|
secondLevelRegistrarDomain +
|
||||||
|
"' is a forbidden registrar-level domain name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {
|
||||||
|
"hostsharing.net", "hostsharing.org", "hostsharing.com", "hostsharing.coop", "hostsharing.de"
|
||||||
|
})
|
||||||
|
void rejectHostsharingDomain(final String secondLevelRegistrarDomain) {
|
||||||
|
// given
|
||||||
|
final var domainSetupBookingItemEntity = HsBookingItemRealEntity.builder()
|
||||||
|
.type(DOMAIN_SETUP)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test-Domain")
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("domainName", secondLevelRegistrarDomain)
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = HsBookingItemEntityValidatorRegistry.doValidate(em, domainSetupBookingItemEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).containsExactly(
|
||||||
|
"'D-12345:Test-Project:Test-Domain.resources.domainName' = '" +
|
||||||
|
secondLevelRegistrarDomain +
|
||||||
|
"' is a forbidden Hostsharing domain name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void containsAllValidations() {
|
||||||
|
// when
|
||||||
|
final var validator = HsBookingItemEntityValidatorRegistry.forType(DOMAIN_SETUP);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||||
|
"{type=string, propertyName=domainName, matchesRegEx=[^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}], matchesRegExDescription=is not a (non-top-level) fully qualified domain name, notMatchesRegEx=[[^.]+, (co|org|gov|ac|sch)\\.uk, (com|net|org|edu|gov|asn|id)\\.au, (co|ne|or|ac|go)\\.jp, (com|net|org|gov|edu|ac)\\.cn, (com|net|org|gov|edu|mil|art)\\.br, (co|net|org|gen|firm|ind)\\.in, (com|net|org|gob|edu)\\.mx, (gov|edu)\\.it, (co|net|org|govt|ac|school|geek|kiwi)\\.nz, (co|ne|or|go|re|pe)\\.kr], notMatchesRegExDescription=is a forbidden registrar-level domain name, maxLength=253, required=true, writeOnce=true}",
|
||||||
|
"{type=string, propertyName=verificationCode, readOnly=true, computed=IN_INIT}");
|
||||||
|
}
|
||||||
|
}
|
@ -9,10 +9,12 @@ 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;
|
||||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.ClassOrderer;
|
import org.junit.jupiter.api.ClassOrderer;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
@ -64,6 +66,11 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
@Autowired
|
@Autowired
|
||||||
JpaAttempt jpaAttempt;
|
JpaAttempt jpaAttempt;
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void cleanup() {
|
||||||
|
Dns.resetFakeResults();
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@Order(2)
|
@Order(2)
|
||||||
class ListAssets {
|
class ListAssets {
|
||||||
@ -249,6 +256,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
void globalAdmin_canAddTopLevelAsset() {
|
void globalAdmin_canAddTopLevelAsset() {
|
||||||
|
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
|
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(() ->
|
||||||
@ -256,6 +264,8 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
.project(givenProject)
|
.project(givenProject)
|
||||||
.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(
|
||||||
|
entry("domainName", "example.com")))
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class DnsUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isRegistrarLevelDomain() {
|
||||||
|
assertThat(Dns.isRegistrarLevelDomain("de")).isTrue();
|
||||||
|
assertThat(Dns.isRegistrarLevelDomain("example.de")).isFalse();
|
||||||
|
|
||||||
|
assertThat(Dns.isRegistrarLevelDomain("co.uk")).isTrue();
|
||||||
|
assertThat(Dns.isRegistrarLevelDomain("example.co.uk")).isFalse();
|
||||||
|
assertThat(Dns.isRegistrarLevelDomain("co.uk.com")).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isRegistrableDomain() {
|
||||||
|
assertThat(Dns.isRegistrableDomain("de")).isFalse();
|
||||||
|
assertThat(Dns.isRegistrableDomain("example.de")).isTrue();
|
||||||
|
assertThat(Dns.isRegistrableDomain("sub.example.de")).isFalse();
|
||||||
|
|
||||||
|
assertThat(Dns.isRegistrableDomain("co.uk")).isFalse();
|
||||||
|
assertThat(Dns.isRegistrableDomain("example.co.uk")).isTrue();
|
||||||
|
assertThat(Dns.isRegistrableDomain("sub.example.co.uk")).isFalse();
|
||||||
|
}
|
||||||
|
}
|
@ -156,9 +156,9 @@ class HsDomainHttpSetupHostingAssetValidatorUnitTest {
|
|||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.htdocsfallback' is expected to be of type Boolean, but is of type String",
|
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.htdocsfallback' is expected to be of type Boolean, but is of type String",
|
||||||
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.fcgi-php-bin' is expected to match any of [^/.*] but 'false' does not match",
|
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.fcgi-php-bin' is expected to match [^/.*] but 'false' does not match",
|
||||||
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match any of [(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))] but '' does not match",
|
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match [(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))] but '' does not match",
|
||||||
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match any of [(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))] but '@' does not match",
|
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match [(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))] but '@' does not match",
|
||||||
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match any of [(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))] but 'example.com' does not match");
|
"'DOMAIN_HTTP_SETUP:example.org|HTTP.config.subdomains' is expected to match [(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))] but 'example.com' does not match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,26 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
|||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
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.booking.item.validators.HsBookingItemEntityValidatorRegistry;
|
||||||
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;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import javax.naming.InvalidNameException;
|
||||||
|
import javax.naming.NameNotFoundException;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.ServiceUnavailableException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static java.util.Map.entry;
|
||||||
|
import static java.util.Map.ofEntries;
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
|
||||||
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.HsHostingAssetType.MANAGED_SERVER;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
||||||
@ -17,62 +29,90 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
|
|
||||||
class HsDomainSetupHostingAssetValidatorUnitTest {
|
class HsDomainSetupHostingAssetValidatorUnitTest {
|
||||||
|
|
||||||
static HsHostingAssetRbacEntity.HsHostingAssetRbacEntityBuilder<?, ?> validEntityBuilder() {
|
public static final Dns.Result DOMAIN_NOT_REGISTERED = Dns.Result.fromException(new NameNotFoundException(
|
||||||
|
"domain not registered"));
|
||||||
|
|
||||||
|
static HsHostingAssetRbacEntity.HsHostingAssetRbacEntityBuilder<?, ?> validEntityBuilder(
|
||||||
|
final String domainName,
|
||||||
|
final Function<HsBookingItemRealEntity.HsBookingItemRealEntityBuilder<?, ?>, HsBookingItemRealEntity> buildBookingItem) {
|
||||||
|
final HsBookingItemRealEntity bookingItem = buildBookingItem.apply(
|
||||||
|
HsBookingItemRealEntity.builder()
|
||||||
|
.type(HsBookingItemType.DOMAIN_SETUP)
|
||||||
|
.resources(new HashMap<>(ofEntries(
|
||||||
|
entry("domainName", domainName)
|
||||||
|
))));
|
||||||
|
HsBookingItemEntityValidatorRegistry.forType(HsBookingItemType.DOMAIN_SETUP).prepareProperties(null, bookingItem);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InvalidDomainNameIdentifier {
|
static HsHostingAssetRbacEntity.HsHostingAssetRbacEntityBuilder<?, ?> validEntityBuilder(final String domainName) {
|
||||||
EMPTY(""),
|
return validEntityBuilder(domainName, HsBookingItemRealEntity.HsBookingItemRealEntityBuilder::build);
|
||||||
TOO_LONG("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456890123456789.de"),
|
}
|
||||||
DASH_AT_BEGINNING("-example.com"),
|
|
||||||
DOT_AT_BEGINNING(".example.com"),
|
@AfterEach
|
||||||
DOT_AT_END("example.com.");
|
void cleanup() {
|
||||||
|
Dns.resetFakeResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
//=====================================================================================================================
|
||||||
|
|
||||||
|
enum InvalidSubDomainNameIdentifierForExampleOrg {
|
||||||
|
IDENTICAL("example.org"),
|
||||||
|
TOO_LONG("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456890123456789.example.org"),
|
||||||
|
DASH_AT_BEGINNING("-sub.example.org"),
|
||||||
|
DOT(".example.org"),
|
||||||
|
DOT_AT_BEGINNING(".sub.example.org"),
|
||||||
|
DOUBLE_DOT("sub..example.com.");
|
||||||
|
|
||||||
final String domainName;
|
final String domainName;
|
||||||
|
|
||||||
InvalidDomainNameIdentifier(final String domainName) {
|
InvalidSubDomainNameIdentifierForExampleOrg(final String domainName) {
|
||||||
this.domainName = domainName;
|
this.domainName = domainName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@EnumSource(InvalidDomainNameIdentifier.class)
|
@EnumSource(InvalidSubDomainNameIdentifierForExampleOrg.class)
|
||||||
void rejectsInvalidIdentifier(final InvalidDomainNameIdentifier testCase) {
|
void rejectsInvalidIdentifier(final InvalidSubDomainNameIdentifierForExampleOrg testCase) {
|
||||||
// given
|
// given
|
||||||
final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build();
|
final var givenEntity = validEntityBuilder(testCase.domainName)
|
||||||
|
.bookingItem(null)
|
||||||
|
.parentAsset(HsHostingAssetRealEntity.builder().type(DOMAIN_SETUP).identifier("example.org").build())
|
||||||
|
.build();
|
||||||
|
// fakeValidDnsVerification(givenEntity);
|
||||||
final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
|
final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validateEntity(givenEntity);
|
final var result = validator.validateEntity(givenEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactly(
|
assertThat(result).contains(
|
||||||
"'identifier' expected to match '^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}', but is '"+testCase.domainName+"'"
|
"'identifier' expected to match '(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))\\.example\\.org', but is '" + testCase.domainName + "'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ValidSubDomainNameIdentifier {
|
||||||
enum ValidDomainNameIdentifier {
|
SIMPLE("sub.example.org"),
|
||||||
SIMPLE("exampe.org"),
|
MAX_LENGTH("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234568901.example.org"),
|
||||||
MAX_LENGTH("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234568901.de"),
|
MIN_LENGTH("x.example.org"),
|
||||||
WITH_DASH("example-test.com"),
|
WITH_DASH("example-test.example.org");
|
||||||
SUBDOMAIN("test.example.com");
|
|
||||||
|
|
||||||
final String domainName;
|
final String domainName;
|
||||||
|
|
||||||
ValidDomainNameIdentifier(final String domainName) {
|
ValidSubDomainNameIdentifier(final String domainName) {
|
||||||
this.domainName = domainName;
|
this.domainName = domainName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@EnumSource(ValidDomainNameIdentifier.class)
|
@EnumSource(ValidSubDomainNameIdentifier.class)
|
||||||
void acceptsValidIdentifier(final ValidDomainNameIdentifier testCase) {
|
void acceptsValidIdentifier(final ValidSubDomainNameIdentifier testCase) {
|
||||||
// given
|
// given
|
||||||
final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build();
|
final var givenEntity = validEntityBuilder(testCase.domainName).identifier(testCase.domainName).build();
|
||||||
|
fakeValidDnsVerification(givenEntity);
|
||||||
final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
|
final var validator = HostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -82,6 +122,13 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
assertThat(result).isEmpty();
|
assertThat(result).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void fakeValidDnsVerification(final HsHostingAssetRbacEntity givenEntity) {
|
||||||
|
final var expectedHash = givenEntity.getBookingItem().getDirectValue("verificationCode", String.class);
|
||||||
|
Dns.fakeResultForDomain(
|
||||||
|
givenEntity.getIdentifier(),
|
||||||
|
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=" + expectedHash));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void containsNoProperties() {
|
void containsNoProperties() {
|
||||||
// when
|
// when
|
||||||
@ -94,10 +141,48 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
void validatesReferencedEntities() {
|
void validatesReferencedEntities() {
|
||||||
// given
|
// given
|
||||||
final var domainSetupHostingAssetEntity = validEntityBuilder()
|
final var domainSetupHostingAssetEntity = validEntityBuilder("example.org",
|
||||||
|
bib -> bib.type(HsBookingItemType.CLOUD_SERVER).build())
|
||||||
.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())
|
||||||
.bookingItem(HsBookingItemRealEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
|
.build();
|
||||||
|
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).contains(
|
||||||
|
"'DOMAIN_SETUP:example.org.bookingItem' or parentItem must be null but is of type CLOUD_SERVER",
|
||||||
|
"'DOMAIN_SETUP:example.org.parentAsset' must be null or of type DOMAIN_SETUP but is of type CLOUD_SERVER",
|
||||||
|
"'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is of type MANAGED_SERVER");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsDomainNameNotMatchingBookingItemDomainName() {
|
||||||
|
// given
|
||||||
|
final var domainSetupHostingAssetEntity = validEntityBuilder("not-matching-booking-item-domain-name.org",
|
||||||
|
bib -> bib.resources(new HashMap<>(ofEntries(
|
||||||
|
entry("domainName", "example.org")
|
||||||
|
))).build()
|
||||||
|
).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(newDomainName)
|
||||||
|
.bookingItem(null)
|
||||||
|
.parentAsset(createValidParentDomainSetupAsset("example.org"))
|
||||||
.build();
|
.build();
|
||||||
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
||||||
|
|
||||||
@ -106,21 +191,248 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
"'DOMAIN_SETUP:example.org.bookingItem' or parentItem must be null but is of type CLOUD_SERVER",
|
"'identifier' expected to match '(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-))\\.example\\.org', " +
|
||||||
"'DOMAIN_SETUP:example.org.parentAsset' must be null or of type DOMAIN_SETUP but is of type CLOUD_SERVER",
|
"but is '" + newDomainName + "'");
|
||||||
"'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is of type MANAGED_SERVER");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void expectsEitherParentAssetOrBookingItem() {
|
void rejectsIfNeitherBookingItemNorParentAssetAreSet() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
final var domainSetupHostingAssetEntity = validEntityBuilder().build();
|
final var domainSetupHostingAssetEntity = validEntityBuilder("example.org")
|
||||||
|
.bookingItem(null)
|
||||||
|
.parentAsset(null)
|
||||||
|
.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
|
||||||
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
assertThat(result).containsExactly("'DOMAIN_SETUP:example.org.bookingItem' must be of type DOMAIN_SETUP but is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DnsLookupFailureTestCase {
|
||||||
|
SERVICE_UNAVAILABLE(
|
||||||
|
new ServiceUnavailableException("no Internet connection"),
|
||||||
|
"[DNS] lookup failed for domain name 'example.org': javax.naming.ServiceUnavailableException: no Internet connection"),
|
||||||
|
NAME_NOT_FOUND(
|
||||||
|
new NameNotFoundException("domain name not found"),
|
||||||
|
null), // no
|
||||||
|
INVALID_NAME(
|
||||||
|
new InvalidNameException("domain name too long or whatever"),
|
||||||
|
"[DNS] invalid domain name 'example.org'"),
|
||||||
|
UNKNOWN_FAILURE(
|
||||||
|
new NamingException("some other problem"),
|
||||||
|
"[DNS] lookup failed for domain name 'example.org': javax.naming.NamingException: some other problem");
|
||||||
|
|
||||||
|
public final NamingException givenException;
|
||||||
|
public final String expectedErrorMessage;
|
||||||
|
|
||||||
|
DnsLookupFailureTestCase(final NamingException givenException, final String expectedErrorMessage) {
|
||||||
|
this.givenException = givenException;
|
||||||
|
this.expectedErrorMessage = expectedErrorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(DnsLookupFailureTestCase.class)
|
||||||
|
void handlesDnsLookupFailures(final DnsLookupFailureTestCase testCase) {
|
||||||
|
|
||||||
|
// given
|
||||||
|
final var domainSetupHostingAssetEntity = validEntityBuilder("example.org").build();
|
||||||
|
Dns.fakeResultForDomain(
|
||||||
|
domainSetupHostingAssetEntity.getIdentifier(),
|
||||||
|
Dns.Result.fromException(testCase.givenException));
|
||||||
|
final var validator = HostingAssetEntityValidatorRegistry.forType(domainSetupHostingAssetEntity.getType());
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = validator.validateEntity(domainSetupHostingAssetEntity);
|
||||||
|
|
||||||
|
// then
|
||||||
|
if (testCase.expectedErrorMessage != null) {
|
||||||
|
assertThat(result).containsExactly(testCase.expectedErrorMessage);
|
||||||
|
} else {
|
||||||
assertThat(result).isEmpty();
|
assertThat(result).isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=====================================================================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allowSetupOfAvailableRegistrableDomain() {
|
||||||
|
domainSetupFor("example.com").notRegistered()
|
||||||
|
.isAccepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allowSetupOfAvailableRegistrable2ndLevelDomain() {
|
||||||
|
domainSetupFor("example.co.uk").notRegistered()
|
||||||
|
.isAccepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectSetupOfRegisteredRegistrable2ndLevelDomainWithoutVerification() {
|
||||||
|
domainSetupFor("example.co.uk").registered()
|
||||||
|
.isRejectedWithCauseMissingVerificationIn("example.co.uk");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allowSetupOfRegisteredRegistrable2ndLevelDomainWithVerification() {
|
||||||
|
domainSetupFor("example.co.uk").registeredWithVerification()
|
||||||
|
.isAccepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectSetupOfExistingRegistrableDomainWithoutValidDnsVerification() {
|
||||||
|
domainSetupFor("example.com").registered()
|
||||||
|
.isRejectedWithCauseMissingVerificationIn("example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allowSetupOfExistingRegistrableDomainWithValidDnsVerification() {
|
||||||
|
domainSetupFor("example.org").registeredWithVerification()
|
||||||
|
.isAccepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allowSetupOfUnregisteredSubdomainWithValidDnsVerificationInSuperDomain() {
|
||||||
|
domainSetupFor("sub.example.org").notRegistered().withVerificationIn("example.org")
|
||||||
|
.isAccepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectSetupOfExistingRegistrableDomainWithInvalidDnsVerification() {
|
||||||
|
domainSetupFor("example.com").registeredWithInvalidVerification()
|
||||||
|
.isRejectedWithCauseMissingVerificationIn("example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void acceptSetupOfRegisteredSubdomainWithInvalidDnsVerificationButValidDnsVerificationInSuperDomain() {
|
||||||
|
domainSetupFor("sub.example.com").registeredWithInvalidVerification().withVerificationIn("example.com")
|
||||||
|
.isAccepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectSetupOfUnregisteredSubdomainWithoutParentAssetAndWithoutDnsVerificationInSuperDomain() {
|
||||||
|
domainSetupFor("sub.example.org").notRegistered()
|
||||||
|
.isRejectedWithCauseMissingVerificationIn("example.org");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void acceptSetupOfUnregisteredSubdomainWithParentAssetEvenWithoutDnsVerificationInSuperDomain() {
|
||||||
|
domainSetupWithParentAssetFor("sub.example.org").notRegistered()
|
||||||
|
.isAccepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allowSetupOfExistingSubdomainWithValidDnsVerificationInSuperDomain() {
|
||||||
|
domainSetupFor("sub.example.org").registered()
|
||||||
|
.withVerificationIn("example.org")
|
||||||
|
.isAccepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectSetupOfExistingSubdomainWithoutDnsVerification() {
|
||||||
|
domainSetupFor("sub.example.org").registered()
|
||||||
|
.isRejectedWithCauseMissingVerificationIn("sub.example.org");
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================================================================================================================
|
||||||
|
|
||||||
|
private static HsHostingAssetRealEntity createValidParentDomainSetupAsset(final String parentDomainName) {
|
||||||
|
final var bookingItem = HsBookingItemRealEntity.builder()
|
||||||
|
.type(HsBookingItemType.DOMAIN_SETUP)
|
||||||
|
.resources(ofEntries(
|
||||||
|
entry("domainName", parentDomainName)
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
final var parentAsset = HsHostingAssetRealEntity.builder()
|
||||||
|
.type(DOMAIN_SETUP)
|
||||||
|
.bookingItem(bookingItem)
|
||||||
|
.identifier(parentDomainName).build();
|
||||||
|
return parentAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomainSetupBuilder {
|
||||||
|
|
||||||
|
private final HsHostingAssetRbacEntity domainAsset;
|
||||||
|
private final String expectedHash;
|
||||||
|
|
||||||
|
public DomainSetupBuilder(final String domainName) {
|
||||||
|
domainAsset = validEntityBuilder(domainName).build();
|
||||||
|
expectedHash = domainAsset.getBookingItem().getDirectValue("verificationCode", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DomainSetupBuilder(final HsHostingAssetRealEntity parentAsset, final String domainName) {
|
||||||
|
domainAsset = validEntityBuilder(domainName)
|
||||||
|
.bookingItem(null)
|
||||||
|
.parentAsset(parentAsset)
|
||||||
|
.build();
|
||||||
|
expectedHash = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainSetupBuilder notRegistered() {
|
||||||
|
Dns.fakeResultForDomain(domainAsset.getIdentifier(), DOMAIN_NOT_REGISTERED);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainSetupBuilder registered() {
|
||||||
|
Dns.fakeResultForDomain(
|
||||||
|
domainAsset.getIdentifier(),
|
||||||
|
Dns.Result.fromRecords());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainSetupBuilder registeredWithInvalidVerification() {
|
||||||
|
Dns.fakeResultForDomain(
|
||||||
|
domainAsset.getIdentifier(),
|
||||||
|
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=SOME-DEFINITELY-WRONG-HASH"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainSetupBuilder registeredWithVerification() {
|
||||||
|
withVerificationIn(domainAsset.getIdentifier());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainSetupBuilder withVerificationIn(final String domainName) {
|
||||||
|
assertThat(expectedHash).as("no expectedHash available").isNotNull();
|
||||||
|
Dns.fakeResultForDomain(
|
||||||
|
domainName,
|
||||||
|
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=" + expectedHash));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void isRejectedWithCauseMissingVerificationIn(final String domainName) {
|
||||||
|
assertThat(expectedHash).as("no expectedHash available").isNotNull();
|
||||||
|
assertThat(validate()).containsAnyOf(
|
||||||
|
"[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=" + expectedHash
|
||||||
|
+ "' found for domain name '" + domainName + "' (nor in its super-domain)",
|
||||||
|
"[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=" + expectedHash
|
||||||
|
+ "' found for domain name '" + domainName + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
void isAccepted() {
|
||||||
|
assertThat(validate()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> validate() {
|
||||||
|
final var validator = HostingAssetEntityValidatorRegistry.forType(DOMAIN_SETUP);
|
||||||
|
return validator.validateEntity(domainAsset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DomainSetupBuilder domainSetupFor(final String domainName) {
|
||||||
|
return new DomainSetupBuilder(domainName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DomainSetupBuilder domainSetupWithParentAssetFor(final String domainName) {
|
||||||
|
return new DomainSetupBuilder(
|
||||||
|
HsHostingAssetRealEntity.builder().type(DOMAIN_SETUP).identifier(Dns.superDomain(domainName).orElseThrow()).build(),
|
||||||
|
domainName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -89,8 +89,8 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
"'EMAIL_ADDRESS:old-local-part@example.org.config.local-part' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowed' does not match",
|
"'EMAIL_ADDRESS:old-local-part@example.org.config.local-part' is expected to match [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowed' does not match",
|
||||||
"'EMAIL_ADDRESS:old-local-part@example.org.config.sub-domain' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowedeither' does not match",
|
"'EMAIL_ADDRESS:old-local-part@example.org.config.sub-domain' is expected to match [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowedeither' does not match",
|
||||||
"'EMAIL_ADDRESS:old-local-part@example.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\.+_-]*)?$, ^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+)?@[a-zA-Z0-9.-]+$, ^nobody$, ^/dev/null$] but 'garbage' does not match any");
|
"'EMAIL_ADDRESS:old-local-part@example.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\.+_-]*)?$, ^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+)?@[a-zA-Z0-9.-]+$, ^nobody$, ^/dev/null$] but 'garbage' does not match any");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
"'UNIX_USER:abc00-temp.config.HDD hard quota' is expected to be at most 0 but is 100",
|
"'UNIX_USER:abc00-temp.config.HDD hard quota' is expected to be at most 0 but is 100",
|
||||||
"'UNIX_USER:abc00-temp.config.HDD soft quota' is expected to be at most 100 but is 200",
|
"'UNIX_USER:abc00-temp.config.HDD soft quota' is expected to be at most 100 but is 200",
|
||||||
"'UNIX_USER:abc00-temp.config.homedir' is readonly but given as '/is/read-only'",
|
"'UNIX_USER:abc00-temp.config.homedir' is readonly but given as '/is/read-only'",
|
||||||
"'UNIX_USER:abc00-temp.config.totpKey' is expected to match any of [^0x([0-9A-Fa-f]{2})+$] but provided value does not match",
|
"'UNIX_USER:abc00-temp.config.totpKey' is expected to match [^0x([0-9A-Fa-f]{2})+$] but provided value does not match",
|
||||||
"'UNIX_USER:abc00-temp.config.password' length is expected to be at min 8 but length of provided value is 5",
|
"'UNIX_USER:abc00-temp.config.password' length is expected to be at min 8 but length of provided value is 5",
|
||||||
"'UNIX_USER:abc00-temp.config.password' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters"
|
"'UNIX_USER:abc00-temp.config.password' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters"
|
||||||
);
|
);
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1352,7 +1352,7 @@ public class ImportHostingAssets extends BaseOfficeDataImport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void importDatabaseUsers(final String[] header, final List<String[]> records) {
|
private void importDatabaseUsers(final String[] header, final List<String[]> records) {
|
||||||
HashGenerator.enableChouldBeHash(true);
|
HashGenerator.enableCouldBeHash(true);
|
||||||
final var columns = new Columns(header);
|
final var columns = new Columns(header);
|
||||||
records.stream()
|
records.stream()
|
||||||
.map(this::trimAll)
|
.map(this::trimAll)
|
||||||
@ -1552,6 +1552,8 @@ public class ImportHostingAssets extends BaseOfficeDataImport {
|
|||||||
.caption("BI " + domainSetup.getIdentifier())
|
.caption("BI " + domainSetup.getIdentifier())
|
||||||
.project((HsBookingProjectRealEntity) relatedProject)
|
.project((HsBookingProjectRealEntity) relatedProject)
|
||||||
//.validity(toPostgresDateRange(created, cancelled))
|
//.validity(toPostgresDateRange(created, cancelled))
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("domainName", domainSetup.getIdentifier())))
|
||||||
.build();
|
.build();
|
||||||
domainSetup.setBookingItem(bookingItem);
|
domainSetup.setBookingItem(bookingItem);
|
||||||
bookingItems.put(nextAvailableBookingItemId(), bookingItem);
|
bookingItems.put(nextAvailableBookingItemId(), bookingItem);
|
||||||
|
Loading…
Reference in New Issue
Block a user