Compare commits

..

3 Commits

Author SHA1 Message Date
Michael Hoennig
7f066f8b6a WIP 2024-10-02 16:37:54 +02:00
Michael Hoennig
3dbcdc6b01 add TODO.impl domain setup to inaccessible users should not be allowed 2024-10-02 12:12:40 +02:00
Michael Hoennig
28c56a4fd6 extract assertEventStatus 2024-10-02 12:12:04 +02:00
6 changed files with 131 additions and 52 deletions

View File

@ -8,14 +8,17 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.lambda.Reducer;
import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import java.net.IDN; import java.net.IDN;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetTypeResource.DOMAIN_DNS_SETUP; import static net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetTypeResource.DOMAIN_DNS_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_HTTP_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_HTTP_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_MBOX_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_MBOX_SETUP;
@ -109,14 +112,31 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
final HsHostingAssetType subAssetType, final HsHostingAssetType subAssetType,
final Function<HsHostingAssetRealEntity.HsHostingAssetRealEntityBuilder<?, ?>, HsHostingAssetRealEntity.HsHostingAssetRealEntityBuilder<?, ?>> builderTransformer) { final Function<HsHostingAssetRealEntity.HsHostingAssetRealEntityBuilder<?, ?>, HsHostingAssetRealEntity.HsHostingAssetRealEntityBuilder<?, ?>> builderTransformer) {
final var resourceType = HsHostingAssetTypeResource.valueOf(subAssetType.name()); final var resourceType = HsHostingAssetTypeResource.valueOf(subAssetType.name());
if (getSubHostingAssetResources().stream().noneMatch(ha -> ha.getType() == resourceType)) { final var subAssetResource = getSubHostingAssetResources().stream()
final var subAssetEntity = builderTransformer.apply( .filter(ha -> ha.getType() == resourceType)
HsHostingAssetRealEntity.builder() .reduce(Reducer::toSingleElement);
.type(HsHostingAssetType.valueOf(subAssetType.name())) final var subAssetEntity = builderTransformer.apply(
.parentAsset(domainSetupAsset)) HsHostingAssetRealEntity.builder()
.build(); .type(subAssetType)
domainSetupAsset.getSubHostingAssets().add(subAssetEntity); .parentAsset(domainSetupAsset))
} .build();
domainSetupAsset.getSubHostingAssets().add(subAssetEntity);
subAssetResource.ifPresent(
res -> {
ofNullable(res.getAssignedToAssetUuid())
.map(uuid -> emw.find(HsHostingAssetRealEntity.class, uuid))
.ifPresent(subAssetEntity::setAssignedToAsset);
ofNullable(res.getAlarmContactUuid())
.map(uuid -> emw.find(HsOfficeContactRealEntity.class, uuid))
.ifPresent(subAssetEntity::setAlarmContact);
ofNullable(res.getIdentifier()).ifPresent(subAssetEntity::setIdentifier);
ofNullable(res.getCaption()).ifPresent(subAssetEntity::setCaption);
res.getConfig();
res.get
}
);
} }
private String getDomainName() { private String getDomainName() {

View File

@ -0,0 +1,8 @@
package net.hostsharing.hsadminng.lambda;
public class Reducer {
public static <T> T toSingleElement(T last, T next) {
throw new AssertionError("only a single entity expected");
}
}

View File

@ -135,9 +135,6 @@ components:
HsHostingAssetSubInsert: HsHostingAssetSubInsert:
type: object type: object
properties: properties:
assignedToAssetUuid:
type: string
format: uuid
type: type:
$ref: '#/components/schemas/HsHostingAssetType' $ref: '#/components/schemas/HsHostingAssetType'
identifier: identifier:
@ -150,6 +147,9 @@ components:
minLength: 3 minLength: 3
maxLength: 80 maxLength: 80
nullable: false nullable: false
assignedToAssetUuid:
type: string
format: uuid
alarmContactUuid: alarmContactUuid:
type: string type: string
format: uuid format: uuid

View File

@ -189,6 +189,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
context.define("superuser-alex@hostsharing.net", "hs_booking.project#D-1000111-D-1000111defaultproject:AGENT"); context.define("superuser-alex@hostsharing.net", "hs_booking.project#D-1000111-D-1000111defaultproject:AGENT");
final var givenProject = findDefaultProjectOfDebitorNumber(1000111); final var givenProject = findDefaultProjectOfDebitorNumber(1000111);
// TODO.impl: "sec01-web" should not work, but does
final var givenUnixUser = realHostingAssetRepo.findByTypeAndIdentifier(UNIX_USER, "fir01-web").stream() final var givenUnixUser = realHostingAssetRepo.findByTypeAndIdentifier(UNIX_USER, "fir01-web").stream()
.findFirst().orElseThrow(); .findFirst().orElseThrow();
@ -200,24 +201,33 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.header("current-subject", "superuser-alex@hostsharing.net") .header("current-subject", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"projectUuid": "{projectUuid}", "projectUuid": "{projectUuid}",
"type": "DOMAIN_SETUP", "type": "DOMAIN_SETUP",
"caption": "Domain-Setup for example.org", "caption": "Domain-Setup for example.org",
"resources": { "resources": {
"domainName": "example.org", "domainName": "example.org",
"verificationCode": "just-a-fake-verification-code" "verificationCode": "just-a-fake-verification-code"
}, },
"asset": { // FIXME: rename to hostingAsset "asset": { // FIXME: rename to hostingAsset
"identifier": "example.org", // also as default for all subAssets "identifier": "example.org", // also as default for all subAssets
"subHostingAssets": [ "subHostingAssets": [
{ {
"type": "DOMAIN_HTTP_SETUP", "type": "DOMAIN_DNS_SETUP"
"assignedToAssetUuid": "{unixUserUuid}" },
} {
] "type": "DOMAIN_HTTP_SETUP",
} "assignedToAssetUuid": "{unixUserUuid}"
} },
{
"type": "DOMAIN_MBOX_SETUP"
},
{
"type": "DOMAIN_SMTP_SETUP"
}
]
}
}
""" """
.replace("{projectUuid}", givenProject.getUuid().toString()) .replace("{projectUuid}", givenProject.getUuid().toString())
.replace("{unixUserUuid}", givenUnixUser.getUuid().toString()) .replace("{unixUserUuid}", givenUnixUser.getUuid().toString())

View File

@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns; import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns;
import net.hostsharing.hsadminng.lambda.Reducer;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapperFake; import net.hostsharing.hsadminng.persistence.EntityManagerWrapperFake;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -33,14 +34,22 @@ class DomainSetupHostingAssetFactoryUnitTest {
private final HsHostingAssetRealEntity managedWebspaceHostingAsset = HsHostingAssetRealEntity.builder() private final HsHostingAssetRealEntity managedWebspaceHostingAsset = HsHostingAssetRealEntity.builder()
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
.identifier("one00")
.build(); .build();
private final HsHostingAssetRealEntity unixUserHostingAsset = HsHostingAssetRealEntity.builder() private final HsHostingAssetRealEntity unixUserHostingAsset = HsHostingAssetRealEntity.builder()
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.type(UNIX_USER) .type(UNIX_USER)
.identifier("one00-web")
.parentAsset(managedWebspaceHostingAsset) .parentAsset(managedWebspaceHostingAsset)
.build(); .build();
private final HsHostingAssetRealEntity anotherManagedWebspaceHostingAsset = HsHostingAssetRealEntity.builder()
.uuid(UUID.randomUUID())
.type(MANAGED_WEBSPACE)
.identifier("two00")
.build();
private EntityManagerWrapperFake emwFake = new EntityManagerWrapperFake(); private EntityManagerWrapperFake emwFake = new EntityManagerWrapperFake();
@Spy @Spy
@ -119,16 +128,8 @@ class DomainSetupHostingAssetFactoryUnitTest {
); );
// then // then
emwFake.stream(BookingItemCreatedEventEntity.class) assertEventStatus(givenBookingItem, givenAssetJson,
.reduce(EntityManagerWrapperFake::toSingleElement) "[[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=null' found for domain name 'example.org' (nor in its super-domain)]");
.map(eventEntity -> {
assertThat(eventEntity.getBookingItem()).isSameAs(givenBookingItem);
assertThat(eventEntity.getAssetJson()).isEqualTo(givenAssetJson);
assertThat(eventEntity.getStatusMessage()).isEqualTo(
"[[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=null' found for domain name 'example.org' (nor in its super-domain)]"
);
return true;
});
} }
@Test @Test
@ -164,15 +165,45 @@ class DomainSetupHostingAssetFactoryUnitTest {
); );
// then // then
emwFake.stream(BookingItemCreatedEventEntity.class) assertEventStatus(givenBookingItem, givenAssetJson,
.reduce(EntityManagerWrapperFake::toSingleElement) "domain DNS setup not allowed for legacy compatibility");
.map(eventEntity -> { }
assertThat(eventEntity.getBookingItem()).isSameAs(givenBookingItem);
assertThat(eventEntity.getAssetJson()).isEqualTo(givenAssetJson); @Test
assertThat(eventEntity.getStatusMessage()).isEqualTo( void persistsEventEntityIfSuppliedDomainUnixUserAndSmtpSetupWebspaceDontMatch() {
"domain DNS setup not allowed for legacy compatibility"); // given
return true; final var givenBookingItem = createBookingItemFromResources(
}); entry("domainName", "example.org"),
entry("verificationCode", "just-a-fake-verification-code")
);
final var givenAssetJson = """
{
"identifier": "example.org", // also as default for all subAssets
"subHostingAssets": [
{
"type": "DOMAIN_HTTP_SETUP",
"assignedToAssetUuid": "{unixUserHostingAssetUuid}"
},
{
"type": "DOMAIN_SMTP_SETUP",
"assignedToAssetUuid": "{managedWebspaceHostingAssetUuid}"
}
]
}
"""
.replace("{unixUserHostingAssetUuid}", unixUserHostingAsset.getUuid().toString())
.replace("{managedWebspaceHostingAssetUuid}", anotherManagedWebspaceHostingAsset.getUuid().toString());
Dns.fakeResultForDomain("example.org",
Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=just-a-fake-verification-code"));
// when
listener.onApplicationEvent(
new BookingItemCreatedAppEvent(this, givenBookingItem, givenAssetJson)
);
// then
assertEventStatus(givenBookingItem, givenAssetJson,
"domain DNS setup not allowed for legacy compatibility");
} }
@SafeVarargs @SafeVarargs
@ -182,4 +213,18 @@ class DomainSetupHostingAssetFactoryUnitTest {
.resources(Map.ofEntries(givenResources)) .resources(Map.ofEntries(givenResources))
.build(); .build();
} }
private void assertEventStatus(
final HsBookingItemRealEntity givenBookingItem,
final String givenAssetJson,
final String expectedErrorMessage) {
emwFake.stream(BookingItemCreatedEventEntity.class)
.reduce(Reducer::toSingleElement)
.map(eventEntity -> {
assertThat(eventEntity.getBookingItem()).isSameAs(givenBookingItem);
assertThat(eventEntity.getAssetJson()).isEqualTo(givenAssetJson);
assertThat(eventEntity.getStatusMessage()).isEqualTo(expectedErrorMessage);
return true;
});
}
} }

View File

@ -15,10 +15,6 @@ public class EntityManagerWrapperFake extends EntityManagerWrapper {
private Map<Class<?>, Map<Object, Object>> entityClasses = new HashMap<>(); private Map<Class<?>, Map<Object, Object>> entityClasses = new HashMap<>();
public static <T> T toSingleElement(T last, T next) {
throw new AssertionError("only a single entity expected");
}
@Override @Override
public boolean contains(final Object entity) { public boolean contains(final Object entity) {
final var id = getEntityId(entity); final var id = getEntityId(entity);