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 78606936..62a62b34 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 @@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetInsertResource; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource; +import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource; import net.hostsharing.hsadminng.mapper.KeyValueMap; import net.hostsharing.hsadminng.mapper.Mapper; import org.springframework.beans.factory.annotation.Autowired; @@ -33,13 +34,15 @@ public class HsHostingAssetController implements HsHostingAssetsApi { @Override @Transactional(readOnly = true) - public ResponseEntity> listAssetsByDebitorUuid( + public ResponseEntity> listAssets( final String currentUser, final String assumedRoles, - final UUID debitorUuid) { + final UUID debitorUuid, + final UUID parentAssetUuid, + final HsHostingAssetTypeResource type) { context.define(currentUser, assumedRoles); - final var entities = assetRepo.findAllByDebitorUuid(debitorUuid); + final var entities = assetRepo.findAllByCriteria(debitorUuid, parentAssetUuid, HsHostingAssetType.of(type)); final var resources = mapper.mapList(entities, HsHostingAssetResource.class); return ResponseEntity.ok(resources); 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 462ccd2c..52466e82 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 @@ -32,7 +32,6 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; -import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; @@ -65,11 +64,11 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; public class HsHostingAssetEntity implements Stringifyable, RbacObject { private static Stringify stringify = stringify(HsHostingAssetEntity.class) - .withProp(HsHostingAssetEntity::getBookingItem) .withProp(HsHostingAssetEntity::getType) - .withProp(HsHostingAssetEntity::getParentAsset) .withProp(HsHostingAssetEntity::getIdentifier) .withProp(HsHostingAssetEntity::getCaption) + .withProp(HsHostingAssetEntity::getParentAsset) + .withProp(HsHostingAssetEntity::getBookingItem) .withProp(HsHostingAssetEntity::getConfig) .quotedValues(false); @@ -122,8 +121,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @Override public String toShortString() { - return ofNullable(bookingItem).map(HsBookingItemEntity::toShortString).orElse("D-???????:?") + - ":" + identifier; + return type + ":" + identifier; } public static RbacView rbac() { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepository.java index 67808097..4926c673 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepository.java @@ -7,16 +7,22 @@ import java.util.List; import java.util.Optional; import java.util.UUID; + public interface HsHostingAssetRepository extends Repository { List findAll(); Optional findByUuid(final UUID serverUuid); @Query(""" - SELECT s FROM HsHostingAssetEntity s - WHERE s.bookingItem.debitor.uuid = :debitorUuid + SELECT asset FROM HsHostingAssetEntity asset + WHERE (:debitorUuid IS NULL OR asset.bookingItem.debitor.uuid = :debitorUuid) + AND (:parentAssetUuid IS NULL OR asset.parentAsset.uuid = :parentAssetUuid) + AND (:type IS NULL OR :type = CAST(asset.type AS String)) """) - List findAllByDebitorUuid(final UUID debitorUuid); + List findAllByCriteriaImpl(UUID debitorUuid, UUID parentAssetUuid, String type); + default List findAllByCriteria(final UUID debitorUuid, final UUID parentAssetUuid, final HsHostingAssetType type) { + return findAllByCriteriaImpl(debitorUuid, parentAssetUuid, HsHostingAssetType.asString(type)); + } HsHostingAssetEntity save(HsHostingAssetEntity current); 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 9e99a8c5..f4040046 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 @@ -1,5 +1,6 @@ package net.hostsharing.hsadminng.hs.hosting.asset; + public enum HsHostingAssetType { CLOUD_SERVER, // named e.g. vm1234 MANAGED_SERVER, // named e.g. vm1234 @@ -25,4 +26,12 @@ public enum HsHostingAssetType { HsHostingAssetType() { this(null); } + + public static > HsHostingAssetType of(final T value) { + return value == null ? null : valueOf(value.name()); + } + + static String asString(final HsHostingAssetType type) { + return type == null ? null : type.name(); + } } diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml index d74766ed..8b81ecc7 100644 --- a/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml @@ -1,18 +1,29 @@ get: - summary: Returns a list of all hosting assets for a specified debitor. - description: Returns the list of all hosting assets for a debitor which are visible to the current user or any of it's assumed roles. + summary: Returns a filtered list of all hosting assets. + description: Returns the list of all hosting assets which match the given filters and are visible to the current user or any of it's assumed roles. tags: - hs-hosting-assets - operationId: listAssetsByDebitorUuid + operationId: listAssets parameters: - $ref: 'auth.yaml#/components/parameters/currentUser' - $ref: 'auth.yaml#/components/parameters/assumedRoles' - name: debitorUuid in: query - required: true + required: false schema: type: string format: uuid + - name: parentAssetUuid + in: query + required: false + schema: + type: string + format: uuid + - name: type + in: query + required: false + schema: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetType' description: The UUID of the debitor, whose hosting assets are to be listed. responses: "200": diff --git a/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql b/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql index cf49baee..6de59816 100644 --- a/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql +++ b/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql @@ -97,7 +97,7 @@ $$; create table RbacObject ( uuid uuid primary key default uuid_generate_v4(), - serialId serial, -- TODO: we might want to remove this once test data deletion works properly + serialId serial, -- TODO.perf: only needed for reverse deletion of temp test data objectTable varchar(64) not null, unique (objectTable, uuid) ); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index d2c73b7c..26d1b763 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -101,6 +101,58 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup """)); // @formatter:on } + + @Test + void globalAdmin_canViewAllAssetsByType() { + + // given + context("superuser-alex@hostsharing.net"); + + RestAssured // @formatter:off + .given() + .header("current-user", "superuser-alex@hostsharing.net") + .port(port) + .when() + .get("http://localhost/api/hs/hosting/assets?type=" + HsHostingAssetType.MANAGED_SERVER) + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + { + "type": "MANAGED_SERVER", + "identifier": "vm1011", + "caption": "some ManagedServer", + "config": { + "CPU": 2, + "SDD": 512, + "extra": 42 + } + }, + { + "type": "MANAGED_SERVER", + "identifier": "vm1013", + "caption": "some ManagedServer", + "config": { + "CPU": 2, + "SDD": 512, + "extra": 42 + } + }, + { + "type": "MANAGED_SERVER", + "identifier": "vm1012", + "caption": "some ManagedServer", + "config": { + "CPU": 2, + "SDD": 512, + "extra": 42 + } + } + ] + """)); + // @formatter:on + } } @Nested @@ -274,7 +326,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup context.define("superuser-alex@hostsharing.net"); assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get() .matches(asset -> { - assertThat(asset.toString()).isEqualTo("HsHostingAssetEntity(D-1000111:some CloudServer, CLOUD_SERVER, vm2001, some test-asset, { CPU: 4, SSD: 4096, something: 1 })"); + assertThat(asset.toString()).isEqualTo("HsHostingAssetEntity(CLOUD_SERVER, vm2001, some test-asset, D-1000111:some CloudServer, { CPU: 4, SSD: 4096, something: 1 })"); return true; }); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java index 4a878bf7..2f0fc00a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java @@ -37,13 +37,13 @@ class HsHostingAssetEntityUnitTest { final var result = givenServer.toString(); assertThat(result).isEqualTo( - "HsHostingAssetEntity(D-1000100:test booking item, MANAGED_WEBSPACE, D-1000100:test booking item:vm1234, xyz00, some managed webspace, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })"); + "HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1000100:test booking item, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })"); } @Test void toShortStringContainsOnlyMemberNumberAndCaption() { final var result = givenServer.toShortString(); - assertThat(result).isEqualTo("D-1000100:test booking item:xyz00"); + assertThat(result).isEqualTo("MANAGED_WEBSPACE:xyz00"); } } 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 2ce1eff6..83a07599 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.MANAGED_SERVER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted; @@ -77,7 +78,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu .bookingItem(givenManagedServer.getBookingItem()) .parentAsset(givenManagedServer) .caption("some new managed webspace") - .type(HsHostingAssetType.MANAGED_WEBSPACE) + .type(MANAGED_WEBSPACE) .identifier("xyz90") .build(); return toCleanup(assetRepo.save(newAsset)); @@ -151,21 +152,19 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu class FindByDebitorUuid { @Test - public void globalAdmin_withoutAssumedRole_canViewAllAssetsOfArbitraryDebitor() { + public void globalAdmin_withoutAssumedRole_canViewArbitraryAssetsOfAllDebitors() { // given context("superuser-alex@hostsharing.net"); - final var debitorUuid = debitorRepo.findDebitorByDebitorNumber(1000212).stream() - .findAny().orElseThrow().getUuid(); // when - final var result = assetRepo.findAllByDebitorUuid(debitorUuid); + final var result = assetRepo.findAllByCriteria(null, null, MANAGED_WEBSPACE); // then allTheseServersAreReturned( result, - "HsHostingAssetEntity(D-1000212:some ManagedServer, MANAGED_WEBSPACE, D-1000212:some PrivateCloud:vm1012, bbb01, some Webspace, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })", - "HsHostingAssetEntity(D-1000212:some PrivateCloud, MANAGED_SERVER, vm1012, some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })", - "HsHostingAssetEntity(D-1000212:some PrivateCloud, CLOUD_SERVER, vm2012, another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })"); + "HsHostingAssetEntity(MANAGED_WEBSPACE, bbb01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })", + "HsHostingAssetEntity(MANAGED_WEBSPACE, aaa01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })", + "HsHostingAssetEntity(MANAGED_WEBSPACE, ccc01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })"); } @Test @@ -175,15 +174,32 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu final var debitorUuid = debitorRepo.findDebitorByDebitorNumber(1000111).stream().findAny().orElseThrow().getUuid(); // when: - final var result = assetRepo.findAllByDebitorUuid(debitorUuid); + final var result = assetRepo.findAllByCriteria(debitorUuid, null, null); // then: exactlyTheseAssetsAreReturned( result, - "HsHostingAssetEntity(D-1000111:some ManagedServer, MANAGED_WEBSPACE, D-1000111:some PrivateCloud:vm1011, aaa01, some Webspace, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })", - "HsHostingAssetEntity(D-1000111:some PrivateCloud, MANAGED_SERVER, vm1011, some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })", - "HsHostingAssetEntity(D-1000111:some PrivateCloud, CLOUD_SERVER, vm2011, another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })"); + "HsHostingAssetEntity(MANAGED_WEBSPACE, aaa01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })", + "HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:some PrivateCloud, { CPU: 2, SDD: 512, extra: 42 })", + "HsHostingAssetEntity(CLOUD_SERVER, vm2011, another CloudServer, D-1000111:some PrivateCloud, { CPU: 2, HDD: 1024, extra: 42 })"); } + + @Test + public void normalUser_canFilterAssetsRelatedToParentAsset() { + // given + context("superuser-alex@hostsharing.net"); + final var parentAssetUuid = assetRepo.findAllByCriteria(null, null, MANAGED_SERVER).stream() + .findAny().orElseThrow().getUuid(); + + // when + final var result = assetRepo.findAllByCriteria(null, parentAssetUuid, null); + + // then + allTheseServersAreReturned( + result, + "HsHostingAssetEntity(MANAGED_WEBSPACE, aaa01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })"); + } + } @Nested @@ -356,8 +372,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu HsHostingAssetEntity givenManagedServer(final String debitorName, final HsHostingAssetType type) { final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).stream().findAny().orElseThrow(); - return assetRepo.findAllByDebitorUuid(givenDebitor.getUuid()).stream() - .filter(i -> i.getType().equals(type)) + return assetRepo.findAllByCriteria(givenDebitor.getUuid(), null, type).stream() .findAny().orElseThrow(); }