From c9e1101035e3ff74957862bda5af55052c85cbe8 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 3 Jul 2024 13:57:39 +0200 Subject: [PATCH 01/13] add domain setup validation --- .../hs/hosting/asset/HsHostingAssetType.java | 7 +- .../HsDomainSetupHostingAssetValidator.java | 27 +++++ ...HsHostingAssetEntityValidatorRegistry.java | 1 + .../hs-hosting/hs-hosting-asset-schemas.yaml | 1 + .../7010-hs-hosting-asset.sql | 10 +- .../7018-hs-hosting-asset-test-data.sql | 5 +- .../HsHostingAssetControllerRestTest.java | 18 +++ ...ingAssetPropsControllerAcceptanceTest.java | 3 +- ...ainSetupHostingAssetValidatorUnitTest.java | 111 ++++++++++++++++++ ...gAssetEntityValidatorRegistryUnitTest.java | 3 +- 10 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidatorUnitTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java index f02a50f0..88ccca45 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java @@ -6,9 +6,10 @@ public enum HsHostingAssetType { MANAGED_SERVER, // named e.g. vm1234 MANAGED_WEBSPACE(MANAGED_SERVER), // named eg. xyz00 UNIX_USER(MANAGED_WEBSPACE), // named e.g. xyz00-abc - DOMAIN_DNS_SETUP(MANAGED_WEBSPACE), // named e.g. example.org - DOMAIN_HTTP_SETUP(MANAGED_WEBSPACE), // named e.g. example.org - DOMAIN_EMAIL_SETUP(MANAGED_WEBSPACE), // named e.g. example.org + DOMAIN_SETUP, // named e.g. example.org + DOMAIN_DNS_SETUP(DOMAIN_SETUP), // named e.g. example.org + DOMAIN_HTTP_SETUP(DOMAIN_SETUP), // named e.g. example.org + DOMAIN_EMAIL_SETUP(DOMAIN_SETUP), // named e.g. example.org // TODO.spec: SECURE_MX EMAIL_ALIAS(MANAGED_WEBSPACE), // named e.g. xyz00-abc diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java new file mode 100644 index 00000000..af671f46 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java @@ -0,0 +1,27 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; + +import java.util.regex.Pattern; + +class HsDomainSetupHostingAssetValidator extends HsHostingAssetEntityValidator { + + private static final String DOMAIN_NAME_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(? type, final HsEntityValidator validator) { diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml index 934c9647..a9ab7f64 100644 --- a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml @@ -10,6 +10,7 @@ components: - MANAGED_SERVER - MANAGED_WEBSPACE - UNIX_USER + - DOMAIN_SETUP - DOMAIN_DNS_SETUP - DOMAIN_HTTP_SETUP - DOMAIN_EMAIL_SETUP diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql index bd6ff6e4..eb335238 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql @@ -9,6 +9,7 @@ create type HsHostingAssetType as enum ( 'MANAGED_SERVER', 'MANAGED_WEBSPACE', 'UNIX_USER', + 'DOMAIN_SETUP', 'DOMAIN_DNS_SETUP', 'DOMAIN_HTTP_SETUP', 'DOMAIN_EMAIL_SETUP', @@ -36,7 +37,7 @@ create table if not exists hs_hosting_asset alarmContactUuid uuid null references hs_office_contact(uuid) initially deferred, constraint chk_hs_hosting_asset_has_booking_item_or_parent_asset - check (bookingItemUuid is not null or parentAssetUuid is not null) + check (bookingItemUuid is not null or parentAssetUuid is not null or type='DOMAIN_SETUP') ); --// @@ -63,9 +64,10 @@ begin when 'MANAGED_SERVER' then null when 'MANAGED_WEBSPACE' then 'MANAGED_SERVER' when 'UNIX_USER' then 'MANAGED_WEBSPACE' - when 'DOMAIN_DNS_SETUP' then 'MANAGED_WEBSPACE' - when 'DOMAIN_HTTP_SETUP' then 'MANAGED_WEBSPACE' - when 'DOMAIN_EMAIL_SETUP' then 'MANAGED_WEBSPACE' + when 'DOMAIN_SETUP' then null + when 'DOMAIN_DNS_SETUP' then 'DOMAIN_SETUP' + when 'DOMAIN_HTTP_SETUP' then 'DOMAIN_SETUP' + when 'DOMAIN_EMAIL_SETUP' then 'DOMAIN_SETUP' when 'EMAIL_ALIAS' then 'MANAGED_WEBSPACE' when 'EMAIL_ADDRESS' then 'DOMAIN_EMAIL_SETUP' when 'PGSQL_USER' then 'MANAGED_WEBSPACE' diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql index 32f2804a..91f9e1dd 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql @@ -23,6 +23,7 @@ declare managedServerUuid uuid; managedWebspaceUuid uuid; webUnixUserUuid uuid; + domainSetupUuid uuid; begin currentTask := 'creating hosting-asset test-data ' || givenProjectCaption; call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); @@ -65,6 +66,7 @@ begin select uuid_generate_v4() into managedServerUuid; select uuid_generate_v4() into managedWebspaceUuid; select uuid_generate_v4() into webUnixUserUuid; + select uuid_generate_v4() into domainSetupUuid; debitorNumberSuffix := relatedDebitor.debitorNumberSuffix; defaultPrefix := relatedDebitor.defaultPrefix; @@ -75,7 +77,8 @@ begin (managedWebspaceUuid, relatedManagedWebspaceBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{}'::jsonb), (uuid_generate_v4(), null, 'EMAIL_ALIAS', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some E-Mail-Alias', '{ "target": [ "office@example.org", "archive@example.com" ] }'::jsonb), (webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024"}'::jsonb), - (uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', managedWebspaceUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*"}'::jsonb); + (domainSetupUuid, null, 'DOMAIN_SETUP', null, null, defaultPrefix || '.example.org', 'some Domain-Setup', '{}'::jsonb), + (uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', domainSetupUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*"}'::jsonb); end; $$; --// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java index 529d34cd..ef70f2c3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java @@ -185,6 +185,24 @@ public class HsHostingAssetControllerRestTest { } } ] + """), + DOMAIN_SETUP( + List.of( + HsHostingAssetEntity.builder() + .type(HsHostingAssetType.DOMAIN_SETUP) + .identifier("example.org") + .caption("some fake Domain-Setup") + .build()), + """ + [ + { + "type": "DOMAIN_SETUP", + "identifier": "example.org", + "caption": "some fake Domain-Setup", + "alarmContact": null, + "config": {} + } + ] """); final HsHostingAssetType assetType; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java index e8323839..1a3d2e0d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java @@ -35,7 +35,8 @@ class HsHostingAssetPropsControllerAcceptanceTest { "MANAGED_WEBSPACE", "CLOUD_SERVER", "UNIX_USER", - "EMAIL_ALIAS" + "EMAIL_ALIAS", + "DOMAIN_SETUP" ] """)); // @formatter:on diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidatorUnitTest.java new file mode 100644 index 00000000..b7d78567 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidatorUnitTest.java @@ -0,0 +1,111 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import java.util.Map; + +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP; +import static org.assertj.core.api.Assertions.assertThat; + +class HsDomainSetupHostingAssetValidatorUnitTest { + + static HsHostingAssetEntityBuilder validEntityBuilder() { + return HsHostingAssetEntity.builder() + .type(DOMAIN_SETUP) + .identifier("example.org"); + } + + enum InvalidDomainNameIdentifier { + EMPTY(""), + TOO_LONG("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456890123456789.de"), + DASH_AT_BEGINNING("-example.com"), + DOT_AT_BEGINNING(".example.com"), + DOT_AT_END("example.com."); + + final String domainName; + + InvalidDomainNameIdentifier(final String domainName) { + this.domainName = domainName; + } + } + + @ParameterizedTest + @EnumSource(InvalidDomainNameIdentifier.class) + void rejectsInvalidIdentifier(final InvalidDomainNameIdentifier testCase) { + // given + final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + final var result = validator.validateEntity(givenEntity); + + // then + assertThat(result).containsExactly( + "'identifier' expected to match '^((?!-)[A-Za-z0-9-]{1,63}(? Date: Wed, 3 Jul 2024 17:32:05 +0200 Subject: [PATCH 02/13] add domain dns setup, WIP --- ...HsDomainDnsSetupHostingAssetValidator.java | 51 ++++++++++ .../HsDomainSetupHostingAssetValidator.java | 2 +- ...HsHostingAssetEntityValidatorRegistry.java | 1 + ...ingAssetPropsControllerAcceptanceTest.java | 3 +- ...DnsSetupHostingAssetValidatorUnitTest.java | 94 +++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java new file mode 100644 index 00000000..f924c71c --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java @@ -0,0 +1,51 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; + +import java.util.regex.Pattern; + +import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf; +import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; +import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; +import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty; + +class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidator { + + final static String RR_REGEX = + "[a-z0-9\\.-]+\\s+" + // a (e.g. domain) name + "([1-9][0-9]*[HDW]\\s+){0,1}" + // optional TTL + "IN\\s+" + // record class IN for Internet + "([1-9][0-9]*[HDW]\\s+){0,1}" + // optional TTL + "[A-Z]+\\s+([a-z0-9\\.-]+|\"[^\"]*\")\\s*" + // either a simple argument or a more complex in quotes + "(;.*)*"; // optional comment at the end of the line + + HsDomainDnsSetupHostingAssetValidator() { + super( BookingItem.mustBeNull(), + ParentAsset.mustBeOfType(HsHostingAssetType.DOMAIN_SETUP), + AssignedToAsset.mustBeNull(), + AlarmContact.isOptional(), + + integerProperty("TTL").min(0).withDefault(21600), + booleanProperty("auto-SOA-RR").withDefault(true), + booleanProperty("auto-NS-RR").withDefault(true), + booleanProperty("auto-MX-RR").withDefault(true), + booleanProperty("auto-A+AAAA-RR").withDefault(true), + booleanProperty("auto-MAILSERVICES-RR").withDefault(true), // incl. AUTOCONFIG_RR, AUTODISCOVER_RR + booleanProperty("auto-DKIM-RR").withDefault(true), + booleanProperty("auto-SPF-RR").withDefault(true), + booleanProperty("auto-WILDCARD-MX-RR").withDefault(true), + booleanProperty("auto-WILDCARD-A+AAAA-RR").withDefault(true), + booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: ask Peter + booleanProperty("auto-WILDCARD-SPF-RR").withDefault(true), + arrayOf( + stringProperty("user-RR").matchesRegEx(RR_REGEX).required() + ).optional()); + } + + @Override + protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { + // FIXME: should be auto-initialized + return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + "$"); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java index af671f46..d2693f7e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java @@ -6,7 +6,7 @@ import java.util.regex.Pattern; class HsDomainSetupHostingAssetValidator extends HsHostingAssetEntityValidator { - private static final String DOMAIN_NAME_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(? type, final HsEntityValidator validator) { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java index 1a3d2e0d..bd571075 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java @@ -36,7 +36,8 @@ class HsHostingAssetPropsControllerAcceptanceTest { "CLOUD_SERVER", "UNIX_USER", "EMAIL_ALIAS", - "DOMAIN_SETUP" + "DOMAIN_SETUP", + "DOMAIN_DNS_SETUP" ] """)); // @formatter:on diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java new file mode 100644 index 00000000..9da99764 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -0,0 +1,94 @@ +package net.hostsharing.hsadminng.hs.hosting.asset.validators; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder; +import net.hostsharing.hsadminng.mapper.Array; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP; +import static org.assertj.core.api.Assertions.assertThat; + +class HsDomainDnsSetupHostingAssetValidatorUnitTest { + + static final HsHostingAssetEntity validDomainSetupEntity = HsHostingAssetEntity.builder() + .type(DOMAIN_SETUP) + .identifier("example.org") + .build(); + + static HsHostingAssetEntityBuilder validEntityBuilder() { + return HsHostingAssetEntity.builder() + .type(DOMAIN_DNS_SETUP) + .parentAsset(validDomainSetupEntity) + .identifier("example.org") + .config(Map.ofEntries( + entry("user-RR", Array.of( + "www IN CNAME example.com. ; www.example.com is an alias for example.com", + "ns IN A 192.0.2.2 ; IPv4 address for ns.example.com") + ) + )); + } + + @Test + void rejectsInvalidIdentifier() { + // given + final var givenEntity = validEntityBuilder().identifier("wrong.org").build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + final var result = validator.validateEntity(givenEntity); + + // then + assertThat(result).containsExactly( + "'identifier' expected to match '^example.org$', but is 'wrong.org'" + ); + } + + @Test + void acceptsValidIdentifier() { + // given + final var givenEntity = validEntityBuilder().identifier(validDomainSetupEntity.getIdentifier()).build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + final var result = validator.validateEntity(givenEntity); + + // then + assertThat(result).isEmpty(); + } + + @Test + void containsNoProperties() { + // when + final var validator = HsHostingAssetEntityValidatorRegistry.forType(CLOUD_SERVER); + + // then + assertThat(validator.properties()).map(Map::toString).isEmpty(); + } + + @Test + void validatesReferencedEntities() { + // given + final var mangedServerHostingAssetEntity = validEntityBuilder() + .parentAsset(HsHostingAssetEntity.builder().build()) + .assignedToAsset(HsHostingAssetEntity.builder().build()) + .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); + + // when + final var result = validator.validateEntity(mangedServerHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'DOMAIN_DNS_SETUP:example.org.bookingItem' must be null but is set to D-???????-?:null", + "'DOMAIN_DNS_SETUP:example.org.parentAsset' must be of type DOMAIN_SETUP but is of type null", + "'DOMAIN_DNS_SETUP:example.org.assignedToAsset' must be null but is set to D-???????-?:null"); + } +} -- 2.39.5 From ba6ea5237eece18427419bd26ed5fbe322d32073 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 07:18:57 +0200 Subject: [PATCH 03/13] splits and improves RR_REGEX and adds more tests --- ...HsDomainDnsSetupHostingAssetValidator.java | 25 +++++++----- ...DnsSetupHostingAssetValidatorUnitTest.java | 39 ++++++++++++++++++- ...gAssetEntityValidatorRegistryUnitTest.java | 3 +- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java index f924c71c..531b645e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java @@ -12,13 +12,18 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidator { - final static String RR_REGEX = - "[a-z0-9\\.-]+\\s+" + // a (e.g. domain) name - "([1-9][0-9]*[HDW]\\s+){0,1}" + // optional TTL - "IN\\s+" + // record class IN for Internet - "([1-9][0-9]*[HDW]\\s+){0,1}" + // optional TTL - "[A-Z]+\\s+([a-z0-9\\.-]+|\"[^\"]*\")\\s*" + // either a simple argument or a more complex in quotes - "(;.*)*"; // optional comment at the end of the line + static final String RR_REGEX_NAME = "([a-z0-9\\.-]+|@)\\s+"; + static final String RR_REGEX_TTL = "(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*"; + static final String RR_REGEX_IN = "IN\\s+"; // record class IN for Internet + static final String RR_RECORD_TYPE = "[A-Z]+\\s+"; + static final String RR_RECORD_DATA = "([a-z0-9\\.-]+|\"[^\"]*\")\\s*"; + static final String RR_COMMENT = "(;.*)*"; + + static final String RR_REGEX_TTL_IN = + RR_REGEX_NAME + RR_REGEX_TTL + RR_REGEX_IN + RR_RECORD_TYPE + RR_RECORD_DATA + RR_COMMENT; + + static final String RR_REGEX_IN_TTL = + RR_REGEX_NAME + RR_REGEX_IN + RR_REGEX_TTL + RR_RECORD_TYPE + RR_RECORD_DATA + RR_COMMENT; HsDomainDnsSetupHostingAssetValidator() { super( BookingItem.mustBeNull(), @@ -31,15 +36,15 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato booleanProperty("auto-NS-RR").withDefault(true), booleanProperty("auto-MX-RR").withDefault(true), booleanProperty("auto-A+AAAA-RR").withDefault(true), - booleanProperty("auto-MAILSERVICES-RR").withDefault(true), // incl. AUTOCONFIG_RR, AUTODISCOVER_RR + booleanProperty("auto-MAILSERVICES-RR").withDefault(true), // TODO. specl: incl. AUTOCONFIG_RR, AUTODISCOVER_RR? booleanProperty("auto-DKIM-RR").withDefault(true), booleanProperty("auto-SPF-RR").withDefault(true), booleanProperty("auto-WILDCARD-MX-RR").withDefault(true), booleanProperty("auto-WILDCARD-A+AAAA-RR").withDefault(true), - booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: ask Peter + booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: @Peter booleanProperty("auto-WILDCARD-SPF-RR").withDefault(true), arrayOf( - stringProperty("user-RR").matchesRegEx(RR_REGEX).required() + stringProperty("user-RR").matchesRegEx(RR_REGEX_TTL_IN, RR_REGEX_IN_TTL).required() ).optional()); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java index 9da99764..eccf8b01 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -13,6 +13,12 @@ import static java.util.Map.entry; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_COMMENT; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_RECORD_DATA; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_RECORD_TYPE; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_REGEX_IN; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_REGEX_NAME; +import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_REGEX_TTL; import static org.assertj.core.api.Assertions.assertThat; class HsDomainDnsSetupHostingAssetValidatorUnitTest { @@ -29,8 +35,10 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { .identifier("example.org") .config(Map.ofEntries( entry("user-RR", Array.of( - "www IN CNAME example.com. ; www.example.com is an alias for example.com", - "ns IN A 192.0.2.2 ; IPv4 address for ns.example.com") + "www IN CNAME example.com. ; www.example.com is an alias for example.com", + "test1 IN 1h30m CNAME example.com.", + "test2 1h30m IN CNAME example.com.", + "ns IN A 192.0.2.2; IPv4 address for ns.example.com") ) )); } @@ -91,4 +99,31 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { "'DOMAIN_DNS_SETUP:example.org.parentAsset' must be of type DOMAIN_SETUP but is of type null", "'DOMAIN_DNS_SETUP:example.org.assignedToAsset' must be null but is set to D-???????-?:null"); } + + @Test + void validStringMatchesRegEx() { + assertThat("@ ").matches(RR_REGEX_NAME); + assertThat("ns ").matches(RR_REGEX_NAME); + assertThat("example.com. ").matches(RR_REGEX_NAME); + + assertThat("12400 ").matches(RR_REGEX_TTL); + assertThat("12400\t\t ").matches(RR_REGEX_TTL); + assertThat("12400 \t\t").matches(RR_REGEX_TTL); + assertThat("1h30m ").matches(RR_REGEX_TTL); + assertThat("30m ").matches(RR_REGEX_TTL); + + assertThat("IN ").matches(RR_REGEX_IN); + assertThat("IN\t\t ").matches(RR_REGEX_IN); + assertThat("IN \t\t").matches(RR_REGEX_IN); + + assertThat("CNAME ").matches(RR_RECORD_TYPE); + assertThat("CNAME\t\t ").matches(RR_RECORD_TYPE); + assertThat("CNAME \t\t").matches(RR_RECORD_TYPE); + + assertThat("example.com.").matches(RR_RECORD_DATA); + assertThat("123.123.123.123").matches(RR_RECORD_DATA); + assertThat("\"some more complex argument; including a semicolon\"").matches(RR_RECORD_DATA); + + assertThat("; whatever ; \" really anything").matches(RR_COMMENT); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistryUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistryUnitTest.java index 7e55d727..4e4abae9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistryUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistryUnitTest.java @@ -34,7 +34,8 @@ class HsHostingAssetEntityValidatorRegistryUnitTest { HsHostingAssetType.MANAGED_WEBSPACE, HsHostingAssetType.UNIX_USER, HsHostingAssetType.EMAIL_ALIAS, - HsHostingAssetType.DOMAIN_SETUP + HsHostingAssetType.DOMAIN_SETUP, + HsHostingAssetType.DOMAIN_DNS_SETUP ); } } -- 2.39.5 From e4343a1777e488383013862e7fec49ef1b464c06 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 07:50:41 +0200 Subject: [PATCH 04/13] generate Zonefile as prep for external validation --- ...HsDomainDnsSetupHostingAssetValidator.java | 18 +++++++++++++ .../hs/validation/HsEntityValidator.java | 24 ++++++++++++++++++ ...DnsSetupHostingAssetValidatorUnitTest.java | 25 +++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java index 531b645e..d0dbf8ca 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java @@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import java.util.regex.Pattern; +import static java.util.Arrays.stream; import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; @@ -53,4 +54,21 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato // FIXME: should be auto-initialized return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + "$"); } + + String toZonefileString(final HsHostingAssetEntity assetEntity) { + return """ + $ORIGIN {domain}. + $TTL {ttl} + + ; these records are just placeholders to create a valid zonefile for the validation + @ 1814400 IN SOA {domain}. root.{domain} ( 1999010100 10800 900 604800 86400 ) + @ IN NS ns + + {userRRs} + """ + .replace("{domain}", assetEntity.getIdentifier()) + .replace("{ttl}", getPropertyValue(assetEntity, "TTL")) + .replace("{userRRs}", getPropertyValues(assetEntity, "user-RR") ); + } + } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java index 13cb3f05..9905d2fa 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -6,7 +6,9 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; +import java.util.stream.Collectors; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; @@ -41,6 +43,12 @@ public abstract class HsEntityValidator { .toList(); } + public final Map> propertiesMap() { + return Arrays.stream(propertyValidators) + .map(ValidatableProperty::toOrderedMap) + .collect(Collectors.toMap(p -> p.get("propertyName").toString(), p -> p)); + } + protected ArrayList validateProperties(final PropertiesProvider propsProvider) { final var result = new ArrayList(); @@ -109,4 +117,20 @@ public abstract class HsEntityValidator { }); return copy; } + + protected String getPropertyValue(final PropertiesProvider entity, final String propertyName) { + final var rawValue = entity.getDirectValue(propertyName, Object.class); + if (rawValue != null) { + return rawValue.toString(); + } + return Objects.toString(propertiesMap().get(propertyName).get("defaultValue")); + } + + protected String getPropertyValues(final PropertiesProvider entity, final String propertyName) { + final var rawValue = entity.getDirectValue(propertyName, Object[].class); + if (rawValue != null) { + return stream(rawValue).map(Object::toString).collect(Collectors.joining("\n")); + } + return ""; + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java index eccf8b01..abe5fff9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -126,4 +126,29 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { assertThat("; whatever ; \" really anything").matches(RR_COMMENT); } + + @Test + void generatesZonefile() { + // given + final var givenEntity = validEntityBuilder().identifier("example.org").build(); + final var validator = (HsDomainDnsSetupHostingAssetValidator) HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + final var zonefile = validator.toZonefileString(givenEntity); + + // then + assertThat(zonefile).isEqualTo(""" + $ORIGIN example.org. + $TTL 21600 + + ; these records are just placeholders to create a valid zonefile for the validation + @ 1814400 IN SOA example.org. root.example.org ( 1999010100 10800 900 604800 86400 ) + @ IN NS ns + + www IN CNAME example.com. ; www.example.com is an alias for example.com + test1 IN 1h30m CNAME example.com. + test2 1h30m IN CNAME example.com. + ns IN A 192.0.2.2; IPv4 address for ns.example.com + """); + } } -- 2.39.5 From 105bd2313cd5413df047aab7aa734e964664d98e Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 13:01:22 +0200 Subject: [PATCH 05/13] split A+AAAA RRs and add parenthesis-variant in record-data --- .../HsDomainDnsSetupHostingAssetValidator.java | 16 ++++++++++------ ...ainDnsSetupHostingAssetValidatorUnitTest.java | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java index d0dbf8ca..f15404e5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java @@ -5,7 +5,6 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import java.util.regex.Pattern; -import static java.util.Arrays.stream; import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; @@ -13,11 +12,13 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidator { + // according to RFC 1035 (section 5) and RFC 1034 static final String RR_REGEX_NAME = "([a-z0-9\\.-]+|@)\\s+"; static final String RR_REGEX_TTL = "(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*"; static final String RR_REGEX_IN = "IN\\s+"; // record class IN for Internet static final String RR_RECORD_TYPE = "[A-Z]+\\s+"; - static final String RR_RECORD_DATA = "([a-z0-9\\.-]+|\"[^\"]*\")\\s*"; + static final String RR_RECORD_DATA_X = "([a-z0-9\\.-]+|\"[^\"]*\")\\s*"; // FIXME: (...) and multiline? + static final String RR_RECORD_DATA = "([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*"; static final String RR_COMMENT = "(;.*)*"; static final String RR_REGEX_TTL_IN = @@ -36,12 +37,16 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato booleanProperty("auto-SOA-RR").withDefault(true), booleanProperty("auto-NS-RR").withDefault(true), booleanProperty("auto-MX-RR").withDefault(true), - booleanProperty("auto-A+AAAA-RR").withDefault(true), - booleanProperty("auto-MAILSERVICES-RR").withDefault(true), // TODO. specl: incl. AUTOCONFIG_RR, AUTODISCOVER_RR? + booleanProperty("auto-A-RR").withDefault(true), + booleanProperty("auto-AAAA-RR").withDefault(true), + booleanProperty("auto-MAILSERVICES-RR").withDefault(true), + booleanProperty("auto-AUTOCONFIG-RR").withDefault(true), // TODO.spec: does that already exist? + booleanProperty("auto-AUTODISCOVER-RR").withDefault(true), booleanProperty("auto-DKIM-RR").withDefault(true), booleanProperty("auto-SPF-RR").withDefault(true), booleanProperty("auto-WILDCARD-MX-RR").withDefault(true), - booleanProperty("auto-WILDCARD-A+AAAA-RR").withDefault(true), + booleanProperty("auto-WILDCARD-A-RR").withDefault(true), + booleanProperty("auto-WILDCARD-AAAA-RR").withDefault(true), booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: @Peter booleanProperty("auto-WILDCARD-SPF-RR").withDefault(true), arrayOf( @@ -70,5 +75,4 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato .replace("{ttl}", getPropertyValue(assetEntity, "TTL")) .replace("{userRRs}", getPropertyValues(assetEntity, "user-RR") ); } - } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java index abe5fff9..bea9bc8f 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -122,6 +122,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { assertThat("example.com.").matches(RR_RECORD_DATA); assertThat("123.123.123.123").matches(RR_RECORD_DATA); + assertThat("(some more complex argument in parenthesis)").matches(RR_RECORD_DATA); assertThat("\"some more complex argument; including a semicolon\"").matches(RR_RECORD_DATA); assertThat("; whatever ; \" really anything").matches(RR_COMMENT); -- 2.39.5 From 977bd595d8b52f7fc598662589e6812881631014 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 13:06:05 +0200 Subject: [PATCH 06/13] fix test for all properties --- ...DnsSetupHostingAssetValidatorUnitTest.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java index bea9bc8f..ae332f07 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -72,12 +72,30 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { } @Test - void containsNoProperties() { + void containsExpectedProperties() { // when - final var validator = HsHostingAssetEntityValidatorRegistry.forType(CLOUD_SERVER); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(DOMAIN_DNS_SETUP); // then - assertThat(validator.properties()).map(Map::toString).isEmpty(); + assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( + "{type=integer, propertyName=TTL, min=0, defaultValue=21600}", + "{type=boolean, propertyName=auto-SOA-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-NS-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-MX-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-A-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-AAAA-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-MAILSERVICES-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-AUTOCONFIG-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-AUTODISCOVER-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-DKIM-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-SPF-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-MX-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-A-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-AAAA-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-DKIM-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-SPF-RR, defaultValue=true}", + "{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*(;.*)*], required=true}}" + ); } @Test -- 2.39.5 From 93774aebe31f89c1892d3673879d1befb21ad48e Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 13:07:03 +0200 Subject: [PATCH 07/13] add SystemProcess as wrapper for ProcessBuilder using Strings for simplified input+output --- .../hsadminng/system/SystemProcess.java | 57 +++++++++++++ .../hsadminng/system/SystemProcessTest.java | 81 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java create mode 100644 src/test/java/net/hostsharing/hsadminng/system/SystemProcessTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java b/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java new file mode 100644 index 00000000..149c6019 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java @@ -0,0 +1,57 @@ +package net.hostsharing.hsadminng.system; + +import lombok.Getter; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; + +public class SystemProcess { + private final ProcessBuilder processBuilder; + + @Getter + private String stdOut; + @Getter + private String stdErr; + + public SystemProcess(final String... command) { + this.processBuilder = new ProcessBuilder(command); + } + + public int execute() throws IOException, InterruptedException { + final var process = processBuilder.start(); + stdOut = fetchOutput(process.getInputStream()); // yeah, twisted ProcessBuilder API + stdErr = fetchOutput(process.getErrorStream()); + return process.waitFor(); + } + + public int execute(final String input) throws IOException, InterruptedException { + final var process = processBuilder.start(); + feedInput(input, process); + stdOut = fetchOutput(process.getInputStream()); // yeah, twisted ProcessBuilder API + stdErr = fetchOutput(process.getErrorStream()); + return process.waitFor(); + } + + private static void feedInput(final String input, final Process process) throws IOException { + try ( + final OutputStreamWriter stdIn = new OutputStreamWriter(process.getOutputStream()); // yeah, twisted ProcessBuilder API + final BufferedWriter writer = new BufferedWriter(stdIn)) { + writer.write(input); + writer.flush(); + } + } + + private static String fetchOutput(final InputStream inputStream) throws IOException { + final var output = new StringBuilder(); + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + for (String line; (line = reader.readLine()) != null; ) { + output.append(line).append(System.lineSeparator()); + } + } + return output.toString(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/system/SystemProcessTest.java b/src/test/java/net/hostsharing/hsadminng/system/SystemProcessTest.java new file mode 100644 index 00000000..e1924adf --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/system/SystemProcessTest.java @@ -0,0 +1,81 @@ +package net.hostsharing.hsadminng.system; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.junit.jupiter.api.condition.OS.LINUX; + +class SystemProcessTest { + + @Test + @EnabledOnOs(LINUX) + void shouldExecuteAndFetchOutput() throws IOException, InterruptedException { + // given + final var process = new SystemProcess("bash", "-c", "echo 'Hello, World!'; echo 'Error!' >&2"); + + // when + final var returnCode = process.execute(); + + // then + assertThat(returnCode).isEqualTo(0); + assertThat(process.getStdOut()).isEqualTo("Hello, World!\n"); + assertThat(process.getStdErr()).isEqualTo("Error!\n"); + } + + @Test + @EnabledOnOs(LINUX) + void shouldReturnErrorCode() throws IOException, InterruptedException { + // given + final var process = new SystemProcess("false"); + + // when + final int returnCode = process.execute(); + + // then + assertThat(returnCode).isEqualTo(1); + } + + @Test + @EnabledOnOs(LINUX) + void shouldExecuteAndFeedInput() throws IOException, InterruptedException { + // given + final var process = new SystemProcess("tr", "[:lower:]", "[:upper:]"); + + // when + final int returnCode = process.execute("Hallo"); + + // then + assertThat(returnCode).isEqualTo(0); + assertThat(process.getStdOut()).isEqualTo("HALLO\n"); + } + + @Test + void shouldThrowExceptionIfProgramNotFound() { + // given + final var process = new SystemProcess("non-existing program"); + + // when + final var exception = catchThrowable(process::execute); + + // then + assertThat(exception).isInstanceOf(IOException.class) + .hasMessage("Cannot run program \"non-existing program\": error=2, No such file or directory"); + } + + @Test + void shouldBeAbleToRunMultipleTimes() throws IOException, InterruptedException { + // given + final var process = new SystemProcess("true"); + + // when + process.execute(); + final int returnCode = process.execute(); + + // then + assertThat(returnCode).isEqualTo(0); + } +} -- 2.39.5 From fe3cbd50fb7a0b5959d3d5221154343744abb745 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 14:02:19 +0200 Subject: [PATCH 08/13] call named-checkzone in validateContext --- ...HsDomainDnsSetupHostingAssetValidator.java | 23 +++++++++- ...DnsSetupHostingAssetValidatorUnitTest.java | 42 +++++++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java index f15404e5..bd6a1cb2 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java @@ -1,10 +1,14 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; +import lombok.SneakyThrows; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; +import net.hostsharing.hsadminng.system.SystemProcess; +import java.util.List; import java.util.regex.Pattern; +import static java.util.Arrays.stream; import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; @@ -17,8 +21,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato static final String RR_REGEX_TTL = "(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*"; static final String RR_REGEX_IN = "IN\\s+"; // record class IN for Internet static final String RR_RECORD_TYPE = "[A-Z]+\\s+"; - static final String RR_RECORD_DATA_X = "([a-z0-9\\.-]+|\"[^\"]*\")\\s*"; // FIXME: (...) and multiline? - static final String RR_RECORD_DATA = "([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*"; + static final String RR_RECORD_DATA = "[^;].*"; static final String RR_COMMENT = "(;.*)*"; static final String RR_REGEX_TTL_IN = @@ -60,6 +63,22 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + "$"); } + @Override + @SneakyThrows + public List validateContext(final HsHostingAssetEntity assetEntity) { + final var result = super.validateContext(assetEntity); + + // TODO.spec: define which checks should get raised to error level + final var namedCheckZone = new SystemProcess("named-checkzone", assetEntity.getIdentifier()); + if (namedCheckZone.execute(toZonefileString(assetEntity)) != 0) { + // yes, named-checkzone writes error messages to stdout + stream(namedCheckZone.getStdOut().split("\n")) + .map(line -> line.replaceAll(" stream-0x[0-9a-f:]+", "")) + .forEach(result::add); + } + return result; + } + String toZonefileString(final HsHostingAssetEntity assetEntity) { return """ $ORIGIN {domain}. diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java index ae332f07..d5b684a7 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -10,7 +10,6 @@ import org.junit.jupiter.api.Test; import java.util.Map; import static java.util.Map.entry; -import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_COMMENT; @@ -35,6 +34,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { .identifier("example.org") .config(Map.ofEntries( entry("user-RR", Array.of( + "@ 1814400 IN XXX example.org. root.example.org ( 1234 10800 900 604800 86400 )", "www IN CNAME example.com. ; www.example.com is an alias for example.com", "test1 IN 1h30m CNAME example.com.", "test2 1h30m IN CNAME example.com.", @@ -94,7 +94,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { "{type=boolean, propertyName=auto-WILDCARD-AAAA-RR, defaultValue=true}", "{type=boolean, propertyName=auto-WILDCARD-DKIM-RR, defaultValue=true}", "{type=boolean, propertyName=auto-WILDCARD-SPF-RR, defaultValue=true}", - "{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+([a-z0-9\\.-]+|\\([^\\)]*\\)|\"[^\"]*\")\\s*(;.*)*], required=true}}" + "{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*], required=true}}" ); } @@ -149,7 +149,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { @Test void generatesZonefile() { // given - final var givenEntity = validEntityBuilder().identifier("example.org").build(); + final var givenEntity = validEntityBuilder().build(); final var validator = (HsDomainDnsSetupHostingAssetValidator) HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); // when @@ -164,10 +164,46 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { @ 1814400 IN SOA example.org. root.example.org ( 1999010100 10800 900 604800 86400 ) @ IN NS ns + @ 1814400 IN XXX example.org. root.example.org ( 1234 10800 900 604800 86400 ) www IN CNAME example.com. ; www.example.com is an alias for example.com test1 IN 1h30m CNAME example.com. test2 1h30m IN CNAME example.com. ns IN A 192.0.2.2; IPv4 address for ns.example.com """); } + + @Test + void acceptsValidEntity() { + // given + final var givenEntity = validEntityBuilder().build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + final var errors = validator.validateEntity(givenEntity); + + // then + assertThat(errors).isEmpty(); + } + + @Test + void rejectsInvalidEntity() { + // given + final var givenEntity = validEntityBuilder().config(Map.ofEntries( + entry("user-RR", Array.of( + "example.org. 1814400 IN SOA example.org. root.example.org (1234 10800 900 604800 86400)" + )) + )) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + final var errors = validator.validateContext(givenEntity); + + // then + assertThat(errors).containsExactlyInAnyOrder( + "dns_master_load: example.org: multiple RRs of singleton type", + "zone example.org/IN: loading from master file (null) failed: multiple RRs of singleton type", + "zone example.org/IN: not loaded due to errors." + ); + } } -- 2.39.5 From 4d5fd34a11fb9c7bccfea847bfc7b5f13d149ddc Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 14:09:58 +0200 Subject: [PATCH 09/13] add Domain DNS Setup example to test data --- .../701-hosting-asset/7018-hs-hosting-asset-test-data.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql index 91f9e1dd..736c129d 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql @@ -78,6 +78,7 @@ begin (uuid_generate_v4(), null, 'EMAIL_ALIAS', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some E-Mail-Alias', '{ "target": [ "office@example.org", "archive@example.com" ] }'::jsonb), (webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024"}'::jsonb), (domainSetupUuid, null, 'DOMAIN_SETUP', null, null, defaultPrefix || '.example.org', 'some Domain-Setup', '{}'::jsonb), + (uuid_generate_v4(), null, 'DOMAIN_DNS_SETUP', domainSetupUuid, null, defaultPrefix || '.example.org', 'some Domain-DNS-Setup', '{}'::jsonb), (uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', domainSetupUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*"}'::jsonb); end; $$; --// -- 2.39.5 From 4c54abb7429311e9b907bad30b7deb6a581dd932 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 14:57:26 +0200 Subject: [PATCH 10/13] preprocessEntity and use Domain-Setup domain in Domain-DNS-Setup --- .../asset/HsHostingAssetController.java | 2 ++ ...HsDomainDnsSetupHostingAssetValidator.java | 13 +++++++++-- .../HsHostingAssetEntityProcessor.java | 23 +++++++++++++++++++ .../hs/validation/HsEntityValidator.java | 7 ++++++ ...DnsSetupHostingAssetValidatorUnitTest.java | 13 +++++++++++ 5 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java index d9b6492f..6e082c05 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java @@ -73,6 +73,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi { final var entity = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); final var mapped = new HsHostingAssetEntityProcessor(entity) + .preprocessEntity() .validateEntity() .prepareForSave() .saveUsing(assetRepo::save) @@ -133,6 +134,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi { new HsHostingAssetEntityPatcher(em, entity).apply(body); final var mapped = new HsHostingAssetEntityProcessor(entity) + .preprocessEntity() .validateEntity() .prepareForSave() .saveUsing(assetRepo::save) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java index bd6a1cb2..e09f77ef 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidator.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.regex.Pattern; import static java.util.Arrays.stream; +import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; @@ -50,7 +51,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato booleanProperty("auto-WILDCARD-MX-RR").withDefault(true), booleanProperty("auto-WILDCARD-A-RR").withDefault(true), booleanProperty("auto-WILDCARD-AAAA-RR").withDefault(true), - booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: @Peter + booleanProperty("auto-WILDCARD-DKIM-RR").withDefault(true), // TODO.spec: check, if that really works booleanProperty("auto-WILDCARD-SPF-RR").withDefault(true), arrayOf( stringProperty("user-RR").matchesRegEx(RR_REGEX_TTL_IN, RR_REGEX_IN_TTL).required() @@ -59,10 +60,17 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato @Override protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { - // FIXME: should be auto-initialized return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + "$"); } + @Override + public void preprocessEntity(final HsHostingAssetEntity entity) { + super.preprocessEntity(entity); + if (entity.getIdentifier() == null) { + ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier())); + } + } + @Override @SneakyThrows public List validateContext(final HsHostingAssetEntity assetEntity) { @@ -80,6 +88,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato } String toZonefileString(final HsHostingAssetEntity assetEntity) { + // TODO.spec: we need to expand the templates (auto-...) in the same way as in Saltstack return """ $ORIGIN {domain}. $TTL {ttl} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityProcessor.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityProcessor.java index 5e270c86..cc192bc7 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityProcessor.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityProcessor.java @@ -14,6 +14,7 @@ import java.util.function.Function; public class HsHostingAssetEntityProcessor { private final HsEntityValidator validator; + private String expectedStep = "preprocessEntity"; private HsHostingAssetEntity entity; private HsHostingAssetResource resource; @@ -22,8 +23,16 @@ public class HsHostingAssetEntityProcessor { this.validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType()); } + /// initial step allowing to set default values before any validations + public HsHostingAssetEntityProcessor preprocessEntity() { + step("preprocessEntity", "validateEntity"); + validator.preprocessEntity(entity); + return this; + } + /// validates the entity itself including its properties public HsHostingAssetEntityProcessor validateEntity() { + step("validateEntity", "prepareForSave"); MultiValidationException.throwIfNotEmpty(validator.validateEntity(entity)); return this; } @@ -31,17 +40,20 @@ public class HsHostingAssetEntityProcessor { /// hashing passwords etc. @SuppressWarnings("unchecked") public HsHostingAssetEntityProcessor prepareForSave() { + step("prepareForSave", "saveUsing"); validator.prepareProperties(entity); return this; } public HsHostingAssetEntityProcessor saveUsing(final Function saveFunction) { + step("saveUsing", "validateContext"); entity = saveFunction.apply(entity); return this; } /// validates the entity within it's parent and child hierarchy (e.g. totals validators and other limits) public HsHostingAssetEntityProcessor validateContext() { + step("validateContext", "mapUsing"); MultiValidationException.throwIfNotEmpty(validator.validateContext(entity)); return this; } @@ -49,6 +61,7 @@ public class HsHostingAssetEntityProcessor { /// maps entity to JSON resource representation public HsHostingAssetEntityProcessor mapUsing( final Function mapFunction) { + step("mapUsing", "revampProperties"); resource = mapFunction.apply(entity); return this; } @@ -56,8 +69,18 @@ public class HsHostingAssetEntityProcessor { /// removes write-only-properties and ads computed-properties @SuppressWarnings("unchecked") public HsHostingAssetResource revampProperties() { + step("revampProperties", null); final var revampedProps = validator.revampProperties(entity, (Map) resource.getConfig()); resource.setConfig(revampedProps); return resource; } + + // Makes sure that the steps are called in the correct order. + // Could also be implemented using an interface per method, but that seems exaggerated. + private void step(final String current, final String next) { + if (!expectedStep.equals(current)) { + throw new IllegalStateException("expected " + expectedStep + " but got " + current); + } + expectedStep = next; + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java index 9905d2fa..de4b70bc 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -49,6 +49,13 @@ public abstract class HsEntityValidator { .collect(Collectors.toMap(p -> p.get("propertyName").toString(), p -> p)); } + /** + Gets called before any validations take place. + Allows to initialize fields and properties to default values. + */ + public void preprocessEntity(final E entity) { + } + protected ArrayList validateProperties(final PropertiesProvider propsProvider) { final var result = new ArrayList(); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java index d5b684a7..c2ec5c52 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -43,6 +43,19 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { )); } + @Test + void preprocessesTakesIdentifierFromParent() { + // given + final var givenEntity = validEntityBuilder().build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + validator.preprocessEntity(givenEntity); + + // then + assertThat(givenEntity.getIdentifier()).isEqualTo(givenEntity.getParentAsset().getIdentifier()); + } + @Test void rejectsInvalidIdentifier() { // given -- 2.39.5 From 3d955fbb851a6f1c7e00394ea4dd2a64ee87d109 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 4 Jul 2024 17:09:32 +0200 Subject: [PATCH 11/13] add integration test anyUser_canCreateNewDomainSetupAsset and (hacked) grants for DOMAIN_DNS_SETUP --- .../hosting/asset/HsHostingAssetEntity.java | 12 ++ .../7013-hs-hosting-asset-rbac.md | 6 +- .../7013-hs-hosting-asset-rbac.sql | 117 ++---------------- ...HostingAssetRepositoryIntegrationTest.java | 30 ++++- 4 files changed, 55 insertions(+), 110 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index ae181921..efd768e0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -41,6 +41,7 @@ import java.util.Map; import java.util.UUID; import static java.util.Collections.emptyMap; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inCaseOf; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL; @@ -51,6 +52,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.UPDATE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.GUEST; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.OWNER; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.REFERRER; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT; @@ -199,6 +201,13 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti directlyFetchedByDependsOnColumn(), NULLABLE) + .switchOnColumn("type", + inCaseOf("DOMAIN_SETUP", then -> { + then.toRole(GLOBAL, GUEST).grantPermission(INSERT); + then.toRole(GLOBAL, ADMIN).grantPermission(SELECT); // FIXME: remove + }) + ) + .createRole(OWNER, (with) -> { with.incomingSuperRole("bookingItem", ADMIN); with.incomingSuperRole("parentAsset", ADMIN); @@ -217,8 +226,11 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti with.outgoingSubRole("bookingItem", TENANT); with.outgoingSubRole("parentAsset", TENANT); with.incomingSuperRole("alarmContact", ADMIN); + with.incomingSuperRole(GLOBAL, GUEST); // FIXME: remove + with.incomingSuperRole(GLOBAL, ADMIN); // FIXME: remove with.permission(SELECT); }) + .limitDiagramTo("asset", "bookingItem", "bookingItem.debitorRel", "parentAsset", "assignedToAsset", "alarmContact", "global"); } diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md index f0b250db..5dbee08b 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md @@ -36,9 +36,9 @@ subgraph asset["`**asset**`"] style asset:permissions fill:#dd4901,stroke:white perm:asset:INSERT{{asset:INSERT}} + perm:asset:SELECT{{asset:SELECT}} perm:asset:DELETE{{asset:DELETE}} perm:asset:UPDATE{{asset:UPDATE}} - perm:asset:SELECT{{asset:SELECT}} end end @@ -99,10 +99,14 @@ role:asset:AGENT ==> role:asset:TENANT role:asset:TENANT ==> role:bookingItem:TENANT role:asset:TENANT ==> role:parentAsset:TENANT role:alarmContact:ADMIN ==> role:asset:TENANT +role:global:GUEST ==> role:asset:TENANT +role:global:ADMIN ==> role:asset:TENANT %% granting permissions to roles role:global:ADMIN ==> perm:asset:INSERT role:parentAsset:ADMIN ==> perm:asset:INSERT +role:global:GUEST ==> perm:asset:INSERT +role:global:ADMIN ==> perm:asset:SELECT role:asset:OWNER ==> perm:asset:DELETE role:asset:ADMIN ==> perm:asset:UPDATE role:asset:TENANT ==> perm:asset:SELECT diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql index cbaffa47..9c5cb70d 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql @@ -75,6 +75,8 @@ begin hsHostingAssetTENANT(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ + globalADMIN(), + globalGUEST(), hsHostingAssetAGENT(NEW), hsOfficeContactADMIN(newAlarmContact)], outgoingSubRoles => array[ @@ -82,6 +84,13 @@ begin hsHostingAssetTENANT(newParentAsset)] ); + IF NEW.type = 'DOMAIN_SETUP' THEN + END IF; + + + + call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), globalAdmin()); + call leaveTriggerForObjectUuid(NEW.uuid); end; $$; @@ -147,114 +156,6 @@ execute procedure updateTriggerForHsHostingAsset_tf(); --// --- ============================================================================ ---changeset hs-hosting-asset-rbac-GRANTING-INSERT-PERMISSION:1 endDelimiter:--// --- ---------------------------------------------------------------------------- - --- granting INSERT permission to global ---------------------------- - -/* - Grants INSERT INTO hs_hosting_asset permissions to specified role of pre-existing global rows. - */ -do language plpgsql $$ - declare - row global; - begin - call defineContext('create INSERT INTO hs_hosting_asset permissions for pre-exising global rows'); - - FOR row IN SELECT * FROM global - -- unconditional for all rows in that table - LOOP - call grantPermissionToRole( - createPermission(row.uuid, 'INSERT', 'hs_hosting_asset'), - globalADMIN()); - END LOOP; - end; -$$; - -/** - Grants hs_hosting_asset INSERT permission to specified role of new global rows. -*/ -create or replace function new_hs_hosting_asset_grants_insert_to_global_tf() - returns trigger - language plpgsql - strict as $$ -begin - -- unconditional for all rows in that table - call grantPermissionToRole( - createPermission(NEW.uuid, 'INSERT', 'hs_hosting_asset'), - globalADMIN()); - -- end. - return NEW; -end; $$; - --- z_... is to put it at the end of after insert triggers, to make sure the roles exist -create trigger z_new_hs_hosting_asset_grants_insert_to_global_tg - after insert on global - for each row -execute procedure new_hs_hosting_asset_grants_insert_to_global_tf(); - --- granting INSERT permission to hs_hosting_asset ---------------------------- - --- Granting INSERT INTO hs_hosting_asset permissions to specified role of pre-existing hs_hosting_asset rows slipped, --- because there cannot yet be any pre-existing rows in the same table yet. - -/** - Grants hs_hosting_asset INSERT permission to specified role of new hs_hosting_asset rows. -*/ -create or replace function new_hs_hosting_asset_grants_insert_to_hs_hosting_asset_tf() - returns trigger - language plpgsql - strict as $$ -begin - -- unconditional for all rows in that table - call grantPermissionToRole( - createPermission(NEW.uuid, 'INSERT', 'hs_hosting_asset'), - hsHostingAssetADMIN(NEW)); - -- end. - return NEW; -end; $$; - --- z_... is to put it at the end of after insert triggers, to make sure the roles exist -create trigger z_new_hs_hosting_asset_grants_insert_to_hs_hosting_asset_tg - after insert on hs_hosting_asset - for each row -execute procedure new_hs_hosting_asset_grants_insert_to_hs_hosting_asset_tf(); - - --- ============================================================================ ---changeset hs_hosting_asset-rbac-CHECKING-INSERT-PERMISSION:1 endDelimiter:--// --- ---------------------------------------------------------------------------- - -/** - Checks if the user respectively the assumed roles are allowed to insert a row to hs_hosting_asset. -*/ -create or replace function hs_hosting_asset_insert_permission_check_tf() - returns trigger - language plpgsql as $$ -declare - superObjectUuid uuid; -begin - -- check INSERT INSERT if global ADMIN - if isGlobalAdmin() then - return NEW; - end if; - -- check INSERT permission via direct foreign key: NEW.parentAssetUuid - if hasInsertPermission(NEW.parentAssetUuid, 'hs_hosting_asset') then - return NEW; - end if; - - raise exception '[403] insert into hs_hosting_asset values(%) not allowed for current subjects % (%)', - NEW, currentSubjects(), currentSubjectsUuids(); -end; $$; - -create trigger hs_hosting_asset_insert_permission_check_tg - before insert on hs_hosting_asset - for each row - execute procedure hs_hosting_asset_insert_permission_check_tf(); ---// - - -- ============================================================================ --changeset hs-hosting-asset-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java index cc8a029b..100d5e95 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -27,6 +27,7 @@ import java.util.Map; import static java.util.Map.entry; 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.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; @@ -153,12 +154,39 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu null)); } + @Test + public void anyUser_canCreateNewDomainSetupAsset() { + // given + context("superuser-alex@hostsharing.net"); + final var assetCount = assetRepo.count(); + + // when + context("person-SmithPeter@example.com"); + final var result = attempt(em, () -> { + final var newAsset = HsHostingAssetEntity.builder() + .caption("some new domain setup") + .type(DOMAIN_SETUP) + .identifier("example.org") + .build(); + return toCleanup(assetRepo.save(newAsset)); + }); + + // then + result.assertSuccessful(); + assertThat(result.returnedValue()).isNotNull().extracting(HsHostingAssetEntity::getUuid).isNotNull(); + assertThat(result.returnedValue().isLoaded()).isFalse(); + context("superuser-alex@hostsharing.net"); + assertThatAssetIsPersisted(result.returnedValue()); + assertThat(assetRepo.count()).isEqualTo(assetCount + 1); + } + private void assertThatAssetIsPersisted(final HsHostingAssetEntity saved) { + final var context = attempt(em, () -> { - context("superuser-alex@hostsharing.net"); final var found = assetRepo.findByUuid(saved.getUuid()); assertThat(found).isNotEmpty().map(HsHostingAssetEntity::toString).get().isEqualTo(saved.toString()); }); + } } -- 2.39.5 From a24d0bb957de9ce7b52a59dbdf2b8cbbb8cebb08 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 5 Jul 2024 06:28:57 +0200 Subject: [PATCH 12/13] add REST test-case for DOMAIN_DNS_SETUP --- .../HsHostingAssetControllerRestTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java index ef70f2c3..eed85585 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java @@ -203,6 +203,49 @@ public class HsHostingAssetControllerRestTest { "config": {} } ] + """), + DOMAIN_DNS_SETUP( + List.of( + HsHostingAssetEntity.builder() + .type(HsHostingAssetType.DOMAIN_DNS_SETUP) + .identifier("example.org") + .caption("some fake Domain-DNS-Setup") + .config(Map.ofEntries( + entry("auto-WILDCARD-MX-RR", false), + entry("auto-WILDCARD-A-RR", false), + entry("auto-WILDCARD-AAAA-RR", false), + entry("auto-WILDCARD-DKIM-RR", false), + entry("auto-WILDCARD-SPF-RR", false), + entry("user-RR", Array.of( + "www IN CNAME example.com. ; www.example.com is an alias for example.com", + "test1 IN 1h30m CNAME example.com.", + "test2 1h30m IN CNAME example.com.", + "ns IN A 192.0.2.2; IPv4 address for ns.example.com") + ) + )) + .build()), + """ + [ + { + "type": "DOMAIN_DNS_SETUP", + "identifier": "example.org", + "caption": "some fake Domain-DNS-Setup", + "alarmContact": null, + "config": { + "auto-WILDCARD-AAAA-RR": false, + "auto-WILDCARD-MX-RR": false, + "auto-WILDCARD-SPF-RR": false, + "auto-WILDCARD-DKIM-RR": false, + "auto-WILDCARD-A-RR": false, + "user-RR": [ + "www IN CNAME example.com. ; www.example.com is an alias for example.com", + "test1 IN 1h30m CNAME example.com.", + "test2 1h30m IN CNAME example.com.", + "ns IN A 192.0.2.2; IPv4 address for ns.example.com" + ] + } + } + ] """); final HsHostingAssetType assetType; -- 2.39.5 From 142810442d31571ddce8afe519557f28f2504ea1 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 5 Jul 2024 11:47:16 +0200 Subject: [PATCH 13/13] amendments according to code-review --- .../hosting/asset/HsHostingAssetEntity.java | 4 +- .../7013-hs-hosting-asset-rbac.md | 2 - .../7013-hs-hosting-asset-rbac.sql | 2 - ...HostingAssetRepositoryIntegrationTest.java | 6 +- ...DnsSetupHostingAssetValidatorUnitTest.java | 105 +++++++++++------- 5 files changed, 69 insertions(+), 50 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index efd768e0..80f9294c 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -204,7 +204,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti .switchOnColumn("type", inCaseOf("DOMAIN_SETUP", then -> { then.toRole(GLOBAL, GUEST).grantPermission(INSERT); - then.toRole(GLOBAL, ADMIN).grantPermission(SELECT); // FIXME: remove + then.toRole(GLOBAL, ADMIN).grantPermission(SELECT); // TODO.spec: replace by a proper solution }) ) @@ -226,8 +226,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti with.outgoingSubRole("bookingItem", TENANT); with.outgoingSubRole("parentAsset", TENANT); with.incomingSuperRole("alarmContact", ADMIN); - with.incomingSuperRole(GLOBAL, GUEST); // FIXME: remove - with.incomingSuperRole(GLOBAL, ADMIN); // FIXME: remove with.permission(SELECT); }) diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md index 5dbee08b..37b47e15 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md @@ -99,8 +99,6 @@ role:asset:AGENT ==> role:asset:TENANT role:asset:TENANT ==> role:bookingItem:TENANT role:asset:TENANT ==> role:parentAsset:TENANT role:alarmContact:ADMIN ==> role:asset:TENANT -role:global:GUEST ==> role:asset:TENANT -role:global:ADMIN ==> role:asset:TENANT %% granting permissions to roles role:global:ADMIN ==> perm:asset:INSERT diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql index 9c5cb70d..5b740226 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql @@ -75,8 +75,6 @@ begin hsHostingAssetTENANT(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ - globalADMIN(), - globalGUEST(), hsHostingAssetAGENT(NEW), hsOfficeContactADMIN(newAlarmContact)], outgoingSubRoles => array[ diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java index 100d5e95..579257a0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -130,6 +130,9 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu .containsExactlyInAnyOrder(fromFormatted( initialGrantNames, + // global-admin + "{ grant perm:hs_hosting_asset#fir00:SELECT to role:global#global:ADMIN by system and assume }", // workaround + // owner "{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_booking_item#fir01:ADMIN by system and assume }", "{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_hosting_asset#vm1011:ADMIN by system and assume }", @@ -138,7 +141,6 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu // admin "{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_hosting_asset#fir00:OWNER by system and assume }", "{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_booking_item#fir01:AGENT by system and assume }", - "{ grant perm:hs_hosting_asset#fir00:INSERT>hs_hosting_asset to role:hs_hosting_asset#fir00:ADMIN by system and assume }", "{ grant perm:hs_hosting_asset#fir00:UPDATE to role:hs_hosting_asset#fir00:ADMIN by system and assume }", // agent @@ -149,7 +151,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu "{ grant role:hs_booking_item#fir01:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }", "{ grant role:hs_hosting_asset#fir00:TENANT to role:hs_hosting_asset#fir00:AGENT by system and assume }", "{ grant role:hs_hosting_asset#vm1011:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }", - "{ grant perm:hs_hosting_asset#fir00:SELECT to role:hs_hosting_asset#fir00:TENANT by system and assume }", + "{ grant perm:hs_hosting_asset#fir00:SELECT to role:hs_hosting_asset#fir00:TENANT by system and assume }", // workaround null)); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java index c2ec5c52..671b9452 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainDnsSetupHostingAssetValidatorUnitTest.java @@ -43,6 +43,33 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { )); } + @Test + void containsExpectedProperties() { + // when + final var validator = HsHostingAssetEntityValidatorRegistry.forType(DOMAIN_DNS_SETUP); + + // then + assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( + "{type=integer, propertyName=TTL, min=0, defaultValue=21600}", + "{type=boolean, propertyName=auto-SOA-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-NS-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-MX-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-A-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-AAAA-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-MAILSERVICES-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-AUTOCONFIG-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-AUTODISCOVER-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-DKIM-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-SPF-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-MX-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-A-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-AAAA-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-DKIM-RR, defaultValue=true}", + "{type=boolean, propertyName=auto-WILDCARD-SPF-RR, defaultValue=true}", + "{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*], required=true}}" + ); + } + @Test void preprocessesTakesIdentifierFromParent() { // given @@ -84,33 +111,6 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { assertThat(result).isEmpty(); } - @Test - void containsExpectedProperties() { - // when - final var validator = HsHostingAssetEntityValidatorRegistry.forType(DOMAIN_DNS_SETUP); - - // then - assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( - "{type=integer, propertyName=TTL, min=0, defaultValue=21600}", - "{type=boolean, propertyName=auto-SOA-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-NS-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-MX-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-A-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-AAAA-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-MAILSERVICES-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-AUTOCONFIG-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-AUTODISCOVER-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-DKIM-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-SPF-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-WILDCARD-MX-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-WILDCARD-A-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-WILDCARD-AAAA-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-WILDCARD-DKIM-RR, defaultValue=true}", - "{type=boolean, propertyName=auto-WILDCARD-SPF-RR, defaultValue=true}", - "{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*], required=true}}" - ); - } - @Test void validatesReferencedEntities() { // given @@ -131,6 +131,42 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { "'DOMAIN_DNS_SETUP:example.org.assignedToAsset' must be null but is set to D-???????-?:null"); } + @Test + void acceptsValidEntity() { + // given + final var givenEntity = validEntityBuilder().build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); + + // when + final var errors = validator.validateEntity(givenEntity); + + // then + assertThat(errors).isEmpty(); + } + + @Test + void recectsInvalidProperties() { + // given + final var mangedServerHostingAssetEntity = validEntityBuilder() + .config(Map.ofEntries( + entry("TTL", "1d30m"), // currently only an integer for seconds is implemented here + entry("user-RR", Array.of( + "@ 1814400 IN 1814400 BAD1 TTL only allowed once", + "www BAD1 Record-Class missing / not enough columns")) + )) + .build(); + final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); + + // when + final var result = validator.validateEntity(mangedServerHostingAssetEntity); + + // then + assertThat(result).containsExactlyInAnyOrder( + "'DOMAIN_DNS_SETUP:example.org.config.TTL' is expected to be of type class java.lang.Integer, but is of type 'String'", + "'DOMAIN_DNS_SETUP:example.org.config.user-RR' is expected to match any of [([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but '@ 1814400 IN 1814400 BAD1 TTL only allowed once' does not match any", + "'DOMAIN_DNS_SETUP:example.org.config.user-RR' is expected to match any of [([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but 'www BAD1 Record-Class missing / not enough columns' does not match any"); + } + @Test void validStringMatchesRegEx() { assertThat("@ ").matches(RR_REGEX_NAME); @@ -186,20 +222,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest { } @Test - void acceptsValidEntity() { - // given - final var givenEntity = validEntityBuilder().build(); - final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); - - // when - final var errors = validator.validateEntity(givenEntity); - - // then - assertThat(errors).isEmpty(); - } - - @Test - void rejectsInvalidEntity() { + void rejectsInvalidZonefile() { // given final var givenEntity = validEntityBuilder().config(Map.ofEntries( entry("user-RR", Array.of( -- 2.39.5