From 66332b6de2a1d02e7e177c0bf7bd1c54b31fa917 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 23 Apr 2024 11:14:48 +0200 Subject: [PATCH] introduce-hosting-module (#46) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/46 Reviewed-by: Timotheus Pokorra --- build.gradle | 12 +- .../asset/HsHostingAssetController.java | 124 +++++ .../hosting/asset/HsHostingAssetEntity.java | 180 +++++++ .../asset/HsHostingAssetEntityPatcher.java | 25 + .../asset/HsHostingAssetRepository.java | 26 + .../hs/hosting/asset/HsHostingAssetType.java | 28 ++ .../hs-hosting/api-mappings.yaml | 17 + .../api-definition/hs-hosting/auth.yaml | 20 + .../hs-hosting/error-responses.yaml | 40 ++ .../hs-hosting/hs-hosting-asset-schemas.yaml | 97 ++++ .../hs-hosting-assets-with-uuid.yaml | 83 ++++ .../hs-hosting/hs-hosting-assets.yaml | 58 +++ .../api-definition/hs-hosting/hs-hosting.yaml | 17 + .../7010-hs-hosting-asset.sql | 42 ++ ...7013-hs-hosting-asset-rbac-CLOUD_SERVER.md | 462 +++++++++++++++++ ...13-hs-hosting-asset-rbac-MANAGED_SERVER.md | 462 +++++++++++++++++ ...-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md | 468 ++++++++++++++++++ .../7013-hs-hosting-asset-rbac.sql | 180 +++++++ .../7018-hs-hosting-asset-test-data.sql | 58 +++ .../db/changelog/db.changelog-master.yaml | 6 + .../hsadminng/arch/ArchitectureTest.java | 13 +- ...sBookingItemRepositoryIntegrationTest.java | 5 +- .../hs/booking/item/TestHsBookingItem.java | 24 + ...sHostingAssetControllerAcceptanceTest.java | 346 +++++++++++++ .../HsHostingAssetEntityPatcherUnitTest.java | 102 ++++ .../asset/HsHostingAssetEntityUnitTest.java | 49 ++ ...HostingAssetRepositoryIntegrationTest.java | 368 ++++++++++++++ .../hs/office/migration/ImportOfficeData.java | 1 + 28 files changed, 3308 insertions(+), 5 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcher.java create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepository.java create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java create mode 100644 src/main/resources/api-definition/hs-hosting/api-mappings.yaml create mode 100644 src/main/resources/api-definition/hs-hosting/auth.yaml create mode 100644 src/main/resources/api-definition/hs-hosting/error-responses.yaml create mode 100644 src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml create mode 100644 src/main/resources/api-definition/hs-hosting/hs-hosting-assets-with-uuid.yaml create mode 100644 src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml create mode 100644 src/main/resources/api-definition/hs-hosting/hs-hosting.yaml create mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql create mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-CLOUD_SERVER.md create mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_SERVER.md create mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md create mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql create mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java diff --git a/build.gradle b/build.gradle index bd48e3ac..332a5410 100644 --- a/build.gradle +++ b/build.gradle @@ -158,6 +158,15 @@ openapiProcessor { showWarnings true openApiNullable true } + springHsHosting { + processorName 'spring' + processor 'io.openapiprocessor:openapi-processor-spring:2022.5' + apiPath "$projectDir/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml" + mapping "$projectDir/src/main/resources/api-definition/hs-hosting/api-mappings.yaml" + targetDir "$buildDir/generated/sources/openapi-javax" + showWarnings true + openApiNullable true + } } sourceSets.main.java.srcDir 'build/generated/sources/openapi' abstract class ProcessSpring extends DefaultTask {} @@ -166,7 +175,8 @@ tasks.register('processSpring', ProcessSpring) 'processSpringRbac', 'processSpringTest', 'processSpringHsOffice', - 'processSpringHsBooking' + 'processSpringHsBooking', + 'processSpringHsHosting' ].each { project.tasks.processSpring.dependsOn it } 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 new file mode 100644 index 00000000..78606936 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java @@ -0,0 +1,124 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetsApi; + +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.mapper.KeyValueMap; +import net.hostsharing.hsadminng.mapper.Mapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; + +import java.util.List; +import java.util.UUID; +import java.util.function.BiConsumer; + + +@RestController +public class HsHostingAssetController implements HsHostingAssetsApi { + + @Autowired + private Context context; + + @Autowired + private Mapper mapper; + + @Autowired + private HsHostingAssetRepository assetRepo; + + @Override + @Transactional(readOnly = true) + public ResponseEntity> listAssetsByDebitorUuid( + final String currentUser, + final String assumedRoles, + final UUID debitorUuid) { + context.define(currentUser, assumedRoles); + + final var entities = assetRepo.findAllByDebitorUuid(debitorUuid); + + final var resources = mapper.mapList(entities, HsHostingAssetResource.class); + return ResponseEntity.ok(resources); + } + + + @Override + @Transactional + public ResponseEntity addAsset( + final String currentUser, + final String assumedRoles, + final HsHostingAssetInsertResource body) { + + context.define(currentUser, assumedRoles); + + final var entityToSave = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); + + final var saved = assetRepo.save(entityToSave); + + final var uri = + MvcUriComponentsBuilder.fromController(getClass()) + .path("/api/hs/hosting/assets/{id}") + .buildAndExpand(saved.getUuid()) + .toUri(); + final var mapped = mapper.map(saved, HsHostingAssetResource.class); + return ResponseEntity.created(uri).body(mapped); + } + + @Override + @Transactional(readOnly = true) + public ResponseEntity getAssetByUuid( + final String currentUser, + final String assumedRoles, + final UUID serverUuid) { + + context.define(currentUser, assumedRoles); + + final var result = assetRepo.findByUuid(serverUuid); + return result + .map(serverEntity -> ResponseEntity.ok( + mapper.map(serverEntity, HsHostingAssetResource.class))) + .orElseGet(() -> ResponseEntity.notFound().build()); + } + + @Override + @Transactional + public ResponseEntity deleteAssetUuid( + final String currentUser, + final String assumedRoles, + final UUID serverUuid) { + context.define(currentUser, assumedRoles); + + final var result = assetRepo.deleteByUuid(serverUuid); + return result == 0 + ? ResponseEntity.notFound().build() + : ResponseEntity.noContent().build(); + } + + @Override + @Transactional + public ResponseEntity patchAsset( + final String currentUser, + final String assumedRoles, + final UUID serverUuid, + final HsHostingAssetPatchResource body) { + + context.define(currentUser, assumedRoles); + + final var current = assetRepo.findByUuid(serverUuid).orElseThrow(); + + new HsHostingAssetEntityPatcher(current).apply(body); + + final var saved = assetRepo.save(current); + final var mapped = mapper.map(saved, HsHostingAssetResource.class); + return ResponseEntity.ok(mapped); + } + + @SuppressWarnings("unchecked") + final BiConsumer RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { + entity.putConfig(KeyValueMap.from(resource.getConfig())); + }; +} 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 new file mode 100644 index 00000000..0d7678e9 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -0,0 +1,180 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import io.hypersistence.utils.hibernate.type.json.JsonType; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; +import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; +import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; +import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; +import net.hostsharing.hsadminng.stringify.Stringify; +import net.hostsharing.hsadminng.stringify.Stringifyable; +import org.hibernate.annotations.Type; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import jakarta.persistence.Version; +import java.io.IOException; +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; +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.usingCase; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; +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.OWNER; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; +import static net.hostsharing.hsadminng.stringify.Stringify.stringify; + +@Builder +@Entity +@Table(name = "hs_hosting_asset_rv") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +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::getConfig) + .quotedValues(false); + + @Id + @GeneratedValue + private UUID uuid; + + @Version + private int version; + + @ManyToOne(optional = false) + @JoinColumn(name = "bookingitemuuid") + private HsBookingItemEntity bookingItem; + + @ManyToOne(optional = true) + @JoinColumn(name = "parentassetuuid") + private HsHostingAssetEntity parentAsset; + + @Column(name = "type") + @Enumerated(EnumType.STRING) + private HsHostingAssetType type; + + @Column(name = "identifier") + private String identifier; // vm1234, xyz00, example.org, xyz00_abc + + @Column(name = "caption") + private String caption; + + @Builder.Default + @Setter(AccessLevel.NONE) + @Type(JsonType.class) + @Column(columnDefinition = "config") + private Map config = new HashMap<>(); + + @Transient + private PatchableMapWrapper configWrapper; + + public PatchableMapWrapper getConfig() { + if ( configWrapper == null ) { + configWrapper = new PatchableMapWrapper(config); + } + return configWrapper; + } + + public void putConfig(Map entries) { + if ( configWrapper == null ) { + configWrapper = new PatchableMapWrapper(config); + } + configWrapper.assign(entries); + } + + @Override + public String toString() { + return stringify.apply(this); + } + + @Override + public String toShortString() { + return ofNullable(bookingItem).map(HsBookingItemEntity::toShortString).orElse("D-???????:?") + + ":" + identifier; + } + + public static RbacView rbac() { + return rbacViewFor("asset", HsHostingAssetEntity.class) + .withIdentityView(SQL.query(""" + SELECT asset.uuid as uuid, bookingItemIV.idName || '-' || cleanIdentifier(asset.identifier) as idName + FROM hs_hosting_asset asset + JOIN hs_booking_item_iv bookingItemIV ON bookingItemIV.uuid = asset.bookingItemUuid + """)) + .withRestrictedViewOrderBy(SQL.expression("identifier")) + .withUpdatableColumns("version", "caption", "config") + + .importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(), + dependsOnColumn("bookingItemUuid"), + directlyFetchedByDependsOnColumn(), + NOT_NULL) + + .switchOnColumn("type", + inCaseOf(CLOUD_SERVER.name(), + then -> then.toRole("bookingItem", AGENT).grantPermission(INSERT)), + inCaseOf(MANAGED_SERVER.name(), + then -> then.toRole("bookingItem", AGENT).grantPermission(INSERT)), + inCaseOf(MANAGED_WEBSPACE.name(), then -> + then.importEntityAlias("parentServer", HsHostingAssetEntity.class, usingCase(MANAGED_SERVER), + dependsOnColumn("parentAssetUuid"), + directlyFetchedByDependsOnColumn(), + NULLABLE) + // TODO.rbac: implement multiple INSERT-rules, e.g. for Asset.bookingItem + Asset.parentAsset + //.toRole("parentServer", AGENT).grantPermission(INSERT) + ) + ) + + .createRole(OWNER, (with) -> { + with.incomingSuperRole("bookingItem", ADMIN); + with.permission(DELETE); + }) + .createSubRole(ADMIN, (with) -> { + with.permission(UPDATE); + }) + .createSubRole(TENANT, (with) -> { + with.outgoingSubRole("bookingItem", TENANT); + with.permission(SELECT); + }); + } + + public static void main(String[] args) throws IOException { + rbac().generateWithBaseFileName("7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac"); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcher.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcher.java new file mode 100644 index 00000000..a555be19 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcher.java @@ -0,0 +1,25 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource; +import net.hostsharing.hsadminng.mapper.EntityPatcher; +import net.hostsharing.hsadminng.mapper.KeyValueMap; +import net.hostsharing.hsadminng.mapper.OptionalFromJson; + +import java.util.Optional; + +public class HsHostingAssetEntityPatcher implements EntityPatcher { + + private final HsHostingAssetEntity entity; + + public HsHostingAssetEntityPatcher(final HsHostingAssetEntity entity) { + this.entity = entity; + } + + @Override + public void apply(final HsHostingAssetPatchResource resource) { + OptionalFromJson.of(resource.getCaption()) + .ifPresent(entity::setCaption); + Optional.ofNullable(resource.getConfig()) + .ifPresent(r -> entity.getConfig().patch(KeyValueMap.from(resource.getConfig()))); + } +} 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 new file mode 100644 index 00000000..67808097 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepository.java @@ -0,0 +1,26 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; + +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 + """) + List findAllByDebitorUuid(final UUID debitorUuid); + + HsHostingAssetEntity save(HsHostingAssetEntity current); + + int deleteByUuid(final UUID uuid); + + long count(); +} 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 new file mode 100644 index 00000000..9e99a8c5 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java @@ -0,0 +1,28 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +public enum HsHostingAssetType { + CLOUD_SERVER, // named e.g. vm1234 + MANAGED_SERVER, // named e.g. vm1234 + MANAGED_WEBSPACE(MANAGED_SERVER), // named eg. xyz00 + UNIX_USER(MANAGED_WEBSPACE), // named e.g. xyz00-abc + DOMAIN_SETUP(UNIX_USER), // named e.g. example.org + + // TODO.spec: SECURE_MX + EMAIL_ALIAS(MANAGED_WEBSPACE), // named e.g. xyz00-abc + EMAIL_ADDRESS(DOMAIN_SETUP), // named e.g. sample@example.org + PGSQL_USER(MANAGED_WEBSPACE), // named e.g. xyz00_abc + PGSQL_DATABASE(MANAGED_WEBSPACE), // named e.g. xyz00_abc, TODO.spec: or PGSQL_USER? + MARIADB_USER(MANAGED_WEBSPACE), // named e.g. xyz00_abc + MARIADB_DATABASE(MANAGED_WEBSPACE); // named e.g. xyz00_abc, TODO.spec: or MARIADB_USER? + + + public final HsHostingAssetType parentAssetType; + + HsHostingAssetType(final HsHostingAssetType parentAssetType) { + this.parentAssetType = parentAssetType; + } + + HsHostingAssetType() { + this(null); + } +} diff --git a/src/main/resources/api-definition/hs-hosting/api-mappings.yaml b/src/main/resources/api-definition/hs-hosting/api-mappings.yaml new file mode 100644 index 00000000..93f3cfe6 --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/api-mappings.yaml @@ -0,0 +1,17 @@ +openapi-processor-mapping: v2 + +options: + package-name: net.hostsharing.hsadminng.hs.hosting.generated.api.v1 + model-name-suffix: Resource + bean-validation: true + +map: + result: org.springframework.http.ResponseEntity + + types: + - type: array => java.util.List + - type: string:uuid => java.util.UUID + + paths: + /api/hs/hosting/assets/{assetUuid}: + null: org.openapitools.jackson.nullable.JsonNullable diff --git a/src/main/resources/api-definition/hs-hosting/auth.yaml b/src/main/resources/api-definition/hs-hosting/auth.yaml new file mode 100644 index 00000000..65d491fb --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/auth.yaml @@ -0,0 +1,20 @@ + +components: + + parameters: + + currentUser: + name: current-user + in: header + required: true + schema: + type: string + description: Identifying name of the currently logged in user. + + assumedRoles: + name: assumed-roles + in: header + required: false + schema: + type: string + description: Semicolon-separated list of roles to assume. The current user needs to have the right to assume these roles. diff --git a/src/main/resources/api-definition/hs-hosting/error-responses.yaml b/src/main/resources/api-definition/hs-hosting/error-responses.yaml new file mode 100644 index 00000000..83ca3dfb --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/error-responses.yaml @@ -0,0 +1,40 @@ +components: + + responses: + NotFound: + description: The specified was not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: The current user is unknown or not authorized. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Forbidden: + description: The current user or none of the assumed or roles is granted access to the resource. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Conflict: + description: The request could not be completed due to a conflict with the current state of the target resource. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + schemas: + + Error: + type: object + properties: + code: + type: string + message: + type: string + required: + - code + - message 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 new file mode 100644 index 00000000..f3ecb6a3 --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml @@ -0,0 +1,97 @@ + +components: + + schemas: + + HsHostingAssetType: + type: string + enum: + - CLOUD_SERVER + - MANAGED_SERVER + - MANAGED_WEBSPACE + - UNIX_USER + - DOMAIN_SETUP + - EMAIL_ALIAS + - EMAIL_ADDRESS + - PGSQL_USER + - PGSQL_DATABASE + - MARIADB_USER + - MARIADB_DATABASE + + HsHostingAsset: + type: object + properties: + uuid: + type: string + format: uuid + type: + $ref: '#/components/schemas/HsHostingAssetType' + identifier: + type: string + caption: + type: string + config: + $ref: '#/components/schemas/HsHostingAssetConfiguration' + required: + - type + - ídentifier + - uuid + - config + + HsHostingAssetPatch: + type: object + properties: + caption: + type: string + nullable: true + config: + $ref: '#/components/schemas/HsHostingAssetConfiguration' + + HsHostingAssetInsert: + type: object + properties: + bookingItemUuid: + type: string + format: uuid + nullable: false + type: + $ref: '#/components/schemas/HsHostingAssetType' + identifier: + type: string + minLength: 3 + maxLength: 80 + nullable: false + caption: + type: string + minLength: 3 + maxLength: 80 + nullable: false + config: + $ref: '#/components/schemas/HsHostingAssetConfiguration' + required: + - type + - identifier + - caption + - debitorUuid + - config + additionalProperties: false + + HsHostingAssetConfiguration: + # forces generating a java.lang.Object containing a Map, instead of class AssetConfiguration + anyOf: + - type: object + properties: + CPU: + type: integer + minimum: 1 + maximum: 16 + SSD: + type: integer + minimum: 16 + maximum: 4096 + HDD: + type: integer + minimum: 16 + maximum: 4096 + additionalProperties: true + diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-assets-with-uuid.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-assets-with-uuid.yaml new file mode 100644 index 00000000..6630d245 --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-assets-with-uuid.yaml @@ -0,0 +1,83 @@ +get: + tags: + - hs-hosting-assets + description: 'Fetch a single managed asset by its uuid, if visible for the current subject.' + operationId: getAssetByUuid + parameters: + - $ref: 'auth.yaml#/components/parameters/currentUser' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: assetUuid + in: path + required: true + schema: + type: string + format: uuid + description: UUID of the hosting asset to fetch. + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAsset' + + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + +patch: + tags: + - hs-hosting-assets + description: 'Updates a single hosting asset identified by its uuid, if permitted for the current subject.' + operationId: patchAsset + parameters: + - $ref: 'auth.yaml#/components/parameters/currentUser' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: assetUuid + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + 'application/json': + schema: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetPatch' + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAsset' + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + +delete: + tags: + - hs-hosting-assets + description: 'Delete a single hosting asset identified by its uuid, if permitted for the current subject.' + operationId: deleteAssetUuid + parameters: + - $ref: 'auth.yaml#/components/parameters/currentUser' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: assetUuid + in: path + required: true + schema: + type: string + format: uuid + description: UUID of the hosting asset to delete. + responses: + "204": + description: No Content + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + "404": + $ref: 'error-responses.yaml#/components/responses/NotFound' 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 new file mode 100644 index 00000000..d74766ed --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml @@ -0,0 +1,58 @@ +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. + tags: + - hs-hosting-assets + operationId: listAssetsByDebitorUuid + parameters: + - $ref: 'auth.yaml#/components/parameters/currentUser' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: debitorUuid + in: query + required: true + schema: + type: string + format: uuid + description: The UUID of the debitor, whose hosting assets are to be listed. + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: array + items: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAsset' + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + +post: + summary: Adds a new hosting asset. + tags: + - hs-hosting-assets + operationId: addAsset + parameters: + - $ref: 'auth.yaml#/components/parameters/currentUser' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + requestBody: + description: A JSON object describing the new hosting asset. + required: true + content: + application/json: + schema: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetInsert' + responses: + "201": + description: Created + content: + 'application/json': + schema: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAsset' + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + "409": + $ref: 'error-responses.yaml#/components/responses/Conflict' diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml new file mode 100644 index 00000000..4f8f29d5 --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml @@ -0,0 +1,17 @@ +openapi: 3.0.3 +info: + title: Hostsharing hsadmin-ng API + version: v0 +servers: + - url: http://localhost:8080 + description: Local development default URL. + +paths: + + # Items + + /api/hs/hosting/assets: + $ref: "hs-hosting-assets.yaml" + + /api/hs/hosting/assets/{assetUuid}: + $ref: "hs-hosting-assets-with-uuid.yaml" 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 new file mode 100644 index 00000000..b827eea8 --- /dev/null +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql @@ -0,0 +1,42 @@ +--liquibase formatted sql + +-- ============================================================================ +--changeset hosting-asset-MAIN-TABLE:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +create type HsHostingAssetType as enum ( + 'CLOUD_SERVER', + 'MANAGED_SERVER', + 'MANAGED_WEBSPACE', + 'UNIX_USER', + 'DOMAIN_SETUP', + 'EMAIL_ALIAS', + 'EMAIL_ADDRESS', + 'PGSQL_USER', + 'PGSQL_DATABASE', + 'MARIADB_USER', + 'MARIADB_DATABASE' +); + +CREATE CAST (character varying as HsHostingAssetType) WITH INOUT AS IMPLICIT; + +create table if not exists hs_hosting_asset +( + uuid uuid unique references RbacObject (uuid), + version int not null default 0, + bookingItemUuid uuid not null references hs_booking_item(uuid), + type HsHostingAssetType, + parentAssetUuid uuid null references hs_hosting_asset(uuid), + identifier varchar(80) not null, + caption varchar(80) not null, + config jsonb not null +); +--// + + +-- ============================================================================ +--changeset hs-hosting-asset-MAIN-TABLE-JOURNAL:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +call create_journal('hs_hosting_asset'); +--// diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-CLOUD_SERVER.md b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-CLOUD_SERVER.md new file mode 100644 index 00000000..3bc75f3b --- /dev/null +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-CLOUD_SERVER.md @@ -0,0 +1,462 @@ +### rbac asset inCaseOf:CLOUD_SERVER + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph parentServer.bookingItem["`**parentServer.bookingItem**`"] + direction TB + style parentServer.bookingItem fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem:roles[ ] + style parentServer.bookingItem:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem:OWNER[[parentServer.bookingItem:OWNER]] + role:parentServer.bookingItem:ADMIN[[parentServer.bookingItem:ADMIN]] + role:parentServer.bookingItem:AGENT[[parentServer.bookingItem:AGENT]] + role:parentServer.bookingItem:TENANT[[parentServer.bookingItem:TENANT]] + end +end + +subgraph parentServer.bookingItem.debitorRel.anchorPerson["`**parentServer.bookingItem.debitorRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.anchorPerson:OWNER[[parentServer.bookingItem.debitorRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitorRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitorRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitorRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel.holderPerson["`**parentServer.bookingItem.debitorRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.holderPerson:OWNER[[parentServer.bookingItem.debitorRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitorRel.holderPerson:ADMIN[[parentServer.bookingItem.debitorRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitorRel.holderPerson:REFERRER[[parentServer.bookingItem.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph parentServer["`**parentServer**`"] + direction TB + style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson["`**parentServer.bookingItem.debitor.partnerRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:OWNER[[parentServer.bookingItem.debitor.partnerRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:ADMIN[[parentServer.bookingItem.debitor.partnerRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:REFERRER[[parentServer.bookingItem.debitor.partnerRel.holderPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel.anchorPerson["`**parentServer.bookingItem.debitor.partnerRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:OWNER[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.debitorRel.anchorPerson["`**bookingItem.debitor.debitorRel.anchorPerson**`"] + direction TB + style bookingItem.debitor.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.anchorPerson:roles[ ] + style bookingItem.debitor.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.anchorPerson:OWNER[[bookingItem.debitor.debitorRel.anchorPerson:OWNER]] + role:bookingItem.debitor.debitorRel.anchorPerson:ADMIN[[bookingItem.debitor.debitorRel.anchorPerson:ADMIN]] + role:bookingItem.debitor.debitorRel.anchorPerson:REFERRER[[bookingItem.debitor.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel.contact["`**parentServer.bookingItem.debitorRel.contact**`"] + direction TB + style parentServer.bookingItem.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.contact:roles[ ] + style parentServer.bookingItem.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.contact:OWNER[[parentServer.bookingItem.debitorRel.contact:OWNER]] + role:parentServer.bookingItem.debitorRel.contact:ADMIN[[parentServer.bookingItem.debitorRel.contact:ADMIN]] + role:parentServer.bookingItem.debitorRel.contact:REFERRER[[parentServer.bookingItem.debitorRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel["`**bookingItem.debitor.partnerRel**`"] + direction TB + style bookingItem.debitor.partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel:roles[ ] + style bookingItem.debitor.partnerRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel:OWNER[[bookingItem.debitor.partnerRel:OWNER]] + role:bookingItem.debitor.partnerRel:ADMIN[[bookingItem.debitor.partnerRel:ADMIN]] + role:bookingItem.debitor.partnerRel:AGENT[[bookingItem.debitor.partnerRel:AGENT]] + role:bookingItem.debitor.partnerRel:TENANT[[bookingItem.debitor.partnerRel:TENANT]] + end +end + +subgraph bookingItem.debitor.partnerRel.anchorPerson["`**bookingItem.debitor.partnerRel.anchorPerson**`"] + direction TB + style bookingItem.debitor.partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.anchorPerson:roles[ ] + style bookingItem.debitor.partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.anchorPerson:OWNER[[bookingItem.debitor.partnerRel.anchorPerson:OWNER]] + role:bookingItem.debitor.partnerRel.anchorPerson:ADMIN[[bookingItem.debitor.partnerRel.anchorPerson:ADMIN]] + role:bookingItem.debitor.partnerRel.anchorPerson:REFERRER[[bookingItem.debitor.partnerRel.anchorPerson:REFERRER]] + end +end + +subgraph bookingItem.debitorRel["`**bookingItem.debitorRel**`"] + direction TB + style bookingItem.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel:roles[ ] + style bookingItem.debitorRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel:OWNER[[bookingItem.debitorRel:OWNER]] + role:bookingItem.debitorRel:ADMIN[[bookingItem.debitorRel:ADMIN]] + role:bookingItem.debitorRel:AGENT[[bookingItem.debitorRel:AGENT]] + role:bookingItem.debitorRel:TENANT[[bookingItem.debitorRel:TENANT]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel.contact["`**parentServer.bookingItem.debitor.partnerRel.contact**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.contact:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.contact:OWNER[[parentServer.bookingItem.debitor.partnerRel.contact:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.contact:ADMIN[[parentServer.bookingItem.debitor.partnerRel.contact:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.contact:REFERRER[[parentServer.bookingItem.debitor.partnerRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitorRel.anchorPerson["`**bookingItem.debitorRel.anchorPerson**`"] + direction TB + style bookingItem.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.anchorPerson:roles[ ] + style bookingItem.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.anchorPerson:OWNER[[bookingItem.debitorRel.anchorPerson:OWNER]] + role:bookingItem.debitorRel.anchorPerson:ADMIN[[bookingItem.debitorRel.anchorPerson:ADMIN]] + role:bookingItem.debitorRel.anchorPerson:REFERRER[[bookingItem.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.debitorRel["`**parentServer.bookingItem.debitor.debitorRel**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel:roles[ ] + style parentServer.bookingItem.debitor.debitorRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel:OWNER[[parentServer.bookingItem.debitor.debitorRel:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel:ADMIN[[parentServer.bookingItem.debitor.debitorRel:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel:AGENT[[parentServer.bookingItem.debitor.debitorRel:AGENT]] + role:parentServer.bookingItem.debitor.debitorRel:TENANT[[parentServer.bookingItem.debitor.debitorRel:TENANT]] + end +end + +subgraph bookingItem.debitorRel.holderPerson["`**bookingItem.debitorRel.holderPerson**`"] + direction TB + style bookingItem.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.holderPerson:roles[ ] + style bookingItem.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.holderPerson:OWNER[[bookingItem.debitorRel.holderPerson:OWNER]] + role:bookingItem.debitorRel.holderPerson:ADMIN[[bookingItem.debitorRel.holderPerson:ADMIN]] + role:bookingItem.debitorRel.holderPerson:REFERRER[[bookingItem.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.refundBankAccount["`**bookingItem.debitor.refundBankAccount**`"] + direction TB + style bookingItem.debitor.refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.refundBankAccount:roles[ ] + style bookingItem.debitor.refundBankAccount:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.refundBankAccount:OWNER[[bookingItem.debitor.refundBankAccount:OWNER]] + role:bookingItem.debitor.refundBankAccount:ADMIN[[bookingItem.debitor.refundBankAccount:ADMIN]] + role:bookingItem.debitor.refundBankAccount:REFERRER[[bookingItem.debitor.refundBankAccount:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel["`**parentServer.bookingItem.debitor.partnerRel**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel:roles[ ] + style parentServer.bookingItem.debitor.partnerRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel:OWNER[[parentServer.bookingItem.debitor.partnerRel:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel:ADMIN[[parentServer.bookingItem.debitor.partnerRel:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel:AGENT[[parentServer.bookingItem.debitor.partnerRel:AGENT]] + role:parentServer.bookingItem.debitor.partnerRel:TENANT[[parentServer.bookingItem.debitor.partnerRel:TENANT]] + end +end + +subgraph bookingItem.debitor.debitorRel.contact["`**bookingItem.debitor.debitorRel.contact**`"] + direction TB + style bookingItem.debitor.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.contact:roles[ ] + style bookingItem.debitor.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.contact:OWNER[[bookingItem.debitor.debitorRel.contact:OWNER]] + role:bookingItem.debitor.debitorRel.contact:ADMIN[[bookingItem.debitor.debitorRel.contact:ADMIN]] + role:bookingItem.debitor.debitorRel.contact:REFERRER[[bookingItem.debitor.debitorRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor["`**parentServer.bookingItem.debitor**`"] + direction TB + style parentServer.bookingItem.debitor fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.debitorRel.holderPerson["`**parentServer.bookingItem.debitor.debitorRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:OWNER[[parentServer.bookingItem.debitor.debitorRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:ADMIN[[parentServer.bookingItem.debitor.debitorRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:REFERRER[[parentServer.bookingItem.debitor.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel.contact["`**bookingItem.debitor.partnerRel.contact**`"] + direction TB + style bookingItem.debitor.partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.contact:roles[ ] + style bookingItem.debitor.partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.contact:OWNER[[bookingItem.debitor.partnerRel.contact:OWNER]] + role:bookingItem.debitor.partnerRel.contact:ADMIN[[bookingItem.debitor.partnerRel.contact:ADMIN]] + role:bookingItem.debitor.partnerRel.contact:REFERRER[[bookingItem.debitor.partnerRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel["`**parentServer.bookingItem.debitorRel**`"] + direction TB + style parentServer.bookingItem.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel:roles[ ] + style parentServer.bookingItem.debitorRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel:OWNER[[parentServer.bookingItem.debitorRel:OWNER]] + role:parentServer.bookingItem.debitorRel:ADMIN[[parentServer.bookingItem.debitorRel:ADMIN]] + role:parentServer.bookingItem.debitorRel:AGENT[[parentServer.bookingItem.debitorRel:AGENT]] + role:parentServer.bookingItem.debitorRel:TENANT[[parentServer.bookingItem.debitorRel:TENANT]] + end +end + +subgraph bookingItem["`**bookingItem**`"] + direction TB + style bookingItem fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem:roles[ ] + style bookingItem:roles fill:#99bcdb,stroke:white + + role:bookingItem:OWNER[[bookingItem:OWNER]] + role:bookingItem:ADMIN[[bookingItem:ADMIN]] + role:bookingItem:AGENT[[bookingItem:AGENT]] + role:bookingItem:TENANT[[bookingItem:TENANT]] + end +end + +subgraph parentServer.parentServer["`**parentServer.parentServer**`"] + direction TB + style parentServer.parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.debitorRel.contact["`**parentServer.bookingItem.debitor.debitorRel.contact**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.contact:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.contact:OWNER[[parentServer.bookingItem.debitor.debitorRel.contact:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.contact:ADMIN[[parentServer.bookingItem.debitor.debitorRel.contact:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.contact:REFERRER[[parentServer.bookingItem.debitor.debitorRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel.holderPerson["`**bookingItem.debitor.partnerRel.holderPerson**`"] + direction TB + style bookingItem.debitor.partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.holderPerson:roles[ ] + style bookingItem.debitor.partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.holderPerson:OWNER[[bookingItem.debitor.partnerRel.holderPerson:OWNER]] + role:bookingItem.debitor.partnerRel.holderPerson:ADMIN[[bookingItem.debitor.partnerRel.holderPerson:ADMIN]] + role:bookingItem.debitor.partnerRel.holderPerson:REFERRER[[bookingItem.debitor.partnerRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitorRel.contact["`**bookingItem.debitorRel.contact**`"] + direction TB + style bookingItem.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.contact:roles[ ] + style bookingItem.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.contact:OWNER[[bookingItem.debitorRel.contact:OWNER]] + role:bookingItem.debitorRel.contact:ADMIN[[bookingItem.debitorRel.contact:ADMIN]] + role:bookingItem.debitorRel.contact:REFERRER[[bookingItem.debitorRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.refundBankAccount["`**parentServer.bookingItem.debitor.refundBankAccount**`"] + direction TB + style parentServer.bookingItem.debitor.refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.refundBankAccount:roles[ ] + style parentServer.bookingItem.debitor.refundBankAccount:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.refundBankAccount:OWNER[[parentServer.bookingItem.debitor.refundBankAccount:OWNER]] + role:parentServer.bookingItem.debitor.refundBankAccount:ADMIN[[parentServer.bookingItem.debitor.refundBankAccount:ADMIN]] + role:parentServer.bookingItem.debitor.refundBankAccount:REFERRER[[parentServer.bookingItem.debitor.refundBankAccount:REFERRER]] + end +end + +subgraph bookingItem.debitor["`**bookingItem.debitor**`"] + direction TB + style bookingItem.debitor fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph bookingItem.debitor.debitorRel.holderPerson["`**bookingItem.debitor.debitorRel.holderPerson**`"] + direction TB + style bookingItem.debitor.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.holderPerson:roles[ ] + style bookingItem.debitor.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.holderPerson:OWNER[[bookingItem.debitor.debitorRel.holderPerson:OWNER]] + role:bookingItem.debitor.debitorRel.holderPerson:ADMIN[[bookingItem.debitor.debitorRel.holderPerson:ADMIN]] + role:bookingItem.debitor.debitorRel.holderPerson:REFERRER[[bookingItem.debitor.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.debitorRel["`**bookingItem.debitor.debitorRel**`"] + direction TB + style bookingItem.debitor.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel:roles[ ] + style bookingItem.debitor.debitorRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel:OWNER[[bookingItem.debitor.debitorRel:OWNER]] + role:bookingItem.debitor.debitorRel:ADMIN[[bookingItem.debitor.debitorRel:ADMIN]] + role:bookingItem.debitor.debitorRel:AGENT[[bookingItem.debitor.debitorRel:AGENT]] + role:bookingItem.debitor.debitorRel:TENANT[[bookingItem.debitor.debitorRel:TENANT]] + end +end + +subgraph asset["`**asset**`"] + direction TB + style asset fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph asset:roles[ ] + style asset:roles fill:#dd4901,stroke:white + + role:asset:OWNER[[asset:OWNER]] + role:asset:ADMIN[[asset:ADMIN]] + role:asset:TENANT[[asset:TENANT]] + end + + subgraph asset:permissions[ ] + style asset:permissions fill:#dd4901,stroke:white + + perm:asset:INSERT{{asset:INSERT}} + perm:asset:DELETE{{asset:DELETE}} + perm:asset:UPDATE{{asset:UPDATE}} + perm:asset:SELECT{{asset:SELECT}} + end +end + +subgraph parentServer.bookingItem.debitor.debitorRel.anchorPerson["`**parentServer.bookingItem.debitor.debitorRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:OWNER[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:REFERRER]] + end +end + +%% granting roles to roles +role:global:ADMIN -.-> role:bookingItem.debitor.refundBankAccount:OWNER +role:bookingItem.debitor.refundBankAccount:OWNER -.-> role:bookingItem.debitor.refundBankAccount:ADMIN +role:bookingItem.debitor.refundBankAccount:ADMIN -.-> role:bookingItem.debitor.refundBankAccount:REFERRER +role:bookingItem.debitor.refundBankAccount:ADMIN -.-> role:bookingItem.debitor.debitorRel:AGENT +role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.refundBankAccount:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitor.partnerRel:OWNER +role:bookingItem.debitor.partnerRel:OWNER -.-> role:bookingItem.debitor.partnerRel:ADMIN +role:bookingItem.debitor.partnerRel:ADMIN -.-> role:bookingItem.debitor.partnerRel:AGENT +role:bookingItem.debitor.partnerRel:AGENT -.-> role:bookingItem.debitor.partnerRel:TENANT +role:bookingItem.debitor.partnerRel:ADMIN -.-> role:bookingItem.debitor.debitorRel:ADMIN +role:bookingItem.debitor.partnerRel:AGENT -.-> role:bookingItem.debitor.debitorRel:AGENT +role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.partnerRel:TENANT +role:global:ADMIN -.-> role:bookingItem.debitorRel.anchorPerson:OWNER +role:bookingItem.debitorRel.anchorPerson:OWNER -.-> role:bookingItem.debitorRel.anchorPerson:ADMIN +role:bookingItem.debitorRel.anchorPerson:ADMIN -.-> role:bookingItem.debitorRel.anchorPerson:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitorRel.holderPerson:OWNER +role:bookingItem.debitorRel.holderPerson:OWNER -.-> role:bookingItem.debitorRel.holderPerson:ADMIN +role:bookingItem.debitorRel.holderPerson:ADMIN -.-> role:bookingItem.debitorRel.holderPerson:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitorRel.contact:OWNER +role:bookingItem.debitorRel.contact:OWNER -.-> role:bookingItem.debitorRel.contact:ADMIN +role:bookingItem.debitorRel.contact:ADMIN -.-> role:bookingItem.debitorRel.contact:REFERRER +role:bookingItem.debitorRel:AGENT -.-> role:bookingItem:OWNER +role:bookingItem:OWNER -.-> role:bookingItem:ADMIN +role:bookingItem.debitorRel:AGENT -.-> role:bookingItem:ADMIN +role:bookingItem:ADMIN -.-> role:bookingItem:AGENT +role:bookingItem:AGENT -.-> role:bookingItem:TENANT +role:bookingItem:TENANT -.-> role:bookingItem.debitorRel:TENANT +role:bookingItem:ADMIN ==> role:asset:OWNER +role:asset:OWNER ==> role:asset:ADMIN +role:asset:ADMIN ==> role:asset:TENANT +role:asset:TENANT ==> role:bookingItem:TENANT + +%% granting permissions to roles +role:bookingItem:AGENT ==> perm:asset:INSERT +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-MANAGED_SERVER.md b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_SERVER.md new file mode 100644 index 00000000..aa856ea9 --- /dev/null +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_SERVER.md @@ -0,0 +1,462 @@ +### rbac asset inCaseOf:MANAGED_SERVER + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph parentServer.bookingItem["`**parentServer.bookingItem**`"] + direction TB + style parentServer.bookingItem fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem:roles[ ] + style parentServer.bookingItem:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem:OWNER[[parentServer.bookingItem:OWNER]] + role:parentServer.bookingItem:ADMIN[[parentServer.bookingItem:ADMIN]] + role:parentServer.bookingItem:AGENT[[parentServer.bookingItem:AGENT]] + role:parentServer.bookingItem:TENANT[[parentServer.bookingItem:TENANT]] + end +end + +subgraph parentServer.bookingItem.debitorRel.anchorPerson["`**parentServer.bookingItem.debitorRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.anchorPerson:OWNER[[parentServer.bookingItem.debitorRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitorRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitorRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitorRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel.holderPerson["`**parentServer.bookingItem.debitorRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.holderPerson:OWNER[[parentServer.bookingItem.debitorRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitorRel.holderPerson:ADMIN[[parentServer.bookingItem.debitorRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitorRel.holderPerson:REFERRER[[parentServer.bookingItem.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph parentServer["`**parentServer**`"] + direction TB + style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson["`**parentServer.bookingItem.debitor.partnerRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:OWNER[[parentServer.bookingItem.debitor.partnerRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:ADMIN[[parentServer.bookingItem.debitor.partnerRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:REFERRER[[parentServer.bookingItem.debitor.partnerRel.holderPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel.anchorPerson["`**parentServer.bookingItem.debitor.partnerRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:OWNER[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.debitorRel.anchorPerson["`**bookingItem.debitor.debitorRel.anchorPerson**`"] + direction TB + style bookingItem.debitor.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.anchorPerson:roles[ ] + style bookingItem.debitor.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.anchorPerson:OWNER[[bookingItem.debitor.debitorRel.anchorPerson:OWNER]] + role:bookingItem.debitor.debitorRel.anchorPerson:ADMIN[[bookingItem.debitor.debitorRel.anchorPerson:ADMIN]] + role:bookingItem.debitor.debitorRel.anchorPerson:REFERRER[[bookingItem.debitor.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel.contact["`**parentServer.bookingItem.debitorRel.contact**`"] + direction TB + style parentServer.bookingItem.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.contact:roles[ ] + style parentServer.bookingItem.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.contact:OWNER[[parentServer.bookingItem.debitorRel.contact:OWNER]] + role:parentServer.bookingItem.debitorRel.contact:ADMIN[[parentServer.bookingItem.debitorRel.contact:ADMIN]] + role:parentServer.bookingItem.debitorRel.contact:REFERRER[[parentServer.bookingItem.debitorRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel["`**bookingItem.debitor.partnerRel**`"] + direction TB + style bookingItem.debitor.partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel:roles[ ] + style bookingItem.debitor.partnerRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel:OWNER[[bookingItem.debitor.partnerRel:OWNER]] + role:bookingItem.debitor.partnerRel:ADMIN[[bookingItem.debitor.partnerRel:ADMIN]] + role:bookingItem.debitor.partnerRel:AGENT[[bookingItem.debitor.partnerRel:AGENT]] + role:bookingItem.debitor.partnerRel:TENANT[[bookingItem.debitor.partnerRel:TENANT]] + end +end + +subgraph bookingItem.debitor.partnerRel.anchorPerson["`**bookingItem.debitor.partnerRel.anchorPerson**`"] + direction TB + style bookingItem.debitor.partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.anchorPerson:roles[ ] + style bookingItem.debitor.partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.anchorPerson:OWNER[[bookingItem.debitor.partnerRel.anchorPerson:OWNER]] + role:bookingItem.debitor.partnerRel.anchorPerson:ADMIN[[bookingItem.debitor.partnerRel.anchorPerson:ADMIN]] + role:bookingItem.debitor.partnerRel.anchorPerson:REFERRER[[bookingItem.debitor.partnerRel.anchorPerson:REFERRER]] + end +end + +subgraph bookingItem.debitorRel["`**bookingItem.debitorRel**`"] + direction TB + style bookingItem.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel:roles[ ] + style bookingItem.debitorRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel:OWNER[[bookingItem.debitorRel:OWNER]] + role:bookingItem.debitorRel:ADMIN[[bookingItem.debitorRel:ADMIN]] + role:bookingItem.debitorRel:AGENT[[bookingItem.debitorRel:AGENT]] + role:bookingItem.debitorRel:TENANT[[bookingItem.debitorRel:TENANT]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel.contact["`**parentServer.bookingItem.debitor.partnerRel.contact**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.contact:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.contact:OWNER[[parentServer.bookingItem.debitor.partnerRel.contact:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.contact:ADMIN[[parentServer.bookingItem.debitor.partnerRel.contact:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.contact:REFERRER[[parentServer.bookingItem.debitor.partnerRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitorRel.anchorPerson["`**bookingItem.debitorRel.anchorPerson**`"] + direction TB + style bookingItem.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.anchorPerson:roles[ ] + style bookingItem.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.anchorPerson:OWNER[[bookingItem.debitorRel.anchorPerson:OWNER]] + role:bookingItem.debitorRel.anchorPerson:ADMIN[[bookingItem.debitorRel.anchorPerson:ADMIN]] + role:bookingItem.debitorRel.anchorPerson:REFERRER[[bookingItem.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.debitorRel["`**parentServer.bookingItem.debitor.debitorRel**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel:roles[ ] + style parentServer.bookingItem.debitor.debitorRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel:OWNER[[parentServer.bookingItem.debitor.debitorRel:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel:ADMIN[[parentServer.bookingItem.debitor.debitorRel:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel:AGENT[[parentServer.bookingItem.debitor.debitorRel:AGENT]] + role:parentServer.bookingItem.debitor.debitorRel:TENANT[[parentServer.bookingItem.debitor.debitorRel:TENANT]] + end +end + +subgraph bookingItem.debitorRel.holderPerson["`**bookingItem.debitorRel.holderPerson**`"] + direction TB + style bookingItem.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.holderPerson:roles[ ] + style bookingItem.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.holderPerson:OWNER[[bookingItem.debitorRel.holderPerson:OWNER]] + role:bookingItem.debitorRel.holderPerson:ADMIN[[bookingItem.debitorRel.holderPerson:ADMIN]] + role:bookingItem.debitorRel.holderPerson:REFERRER[[bookingItem.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.refundBankAccount["`**bookingItem.debitor.refundBankAccount**`"] + direction TB + style bookingItem.debitor.refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.refundBankAccount:roles[ ] + style bookingItem.debitor.refundBankAccount:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.refundBankAccount:OWNER[[bookingItem.debitor.refundBankAccount:OWNER]] + role:bookingItem.debitor.refundBankAccount:ADMIN[[bookingItem.debitor.refundBankAccount:ADMIN]] + role:bookingItem.debitor.refundBankAccount:REFERRER[[bookingItem.debitor.refundBankAccount:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel["`**parentServer.bookingItem.debitor.partnerRel**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel:roles[ ] + style parentServer.bookingItem.debitor.partnerRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel:OWNER[[parentServer.bookingItem.debitor.partnerRel:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel:ADMIN[[parentServer.bookingItem.debitor.partnerRel:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel:AGENT[[parentServer.bookingItem.debitor.partnerRel:AGENT]] + role:parentServer.bookingItem.debitor.partnerRel:TENANT[[parentServer.bookingItem.debitor.partnerRel:TENANT]] + end +end + +subgraph bookingItem.debitor.debitorRel.contact["`**bookingItem.debitor.debitorRel.contact**`"] + direction TB + style bookingItem.debitor.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.contact:roles[ ] + style bookingItem.debitor.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.contact:OWNER[[bookingItem.debitor.debitorRel.contact:OWNER]] + role:bookingItem.debitor.debitorRel.contact:ADMIN[[bookingItem.debitor.debitorRel.contact:ADMIN]] + role:bookingItem.debitor.debitorRel.contact:REFERRER[[bookingItem.debitor.debitorRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor["`**parentServer.bookingItem.debitor**`"] + direction TB + style parentServer.bookingItem.debitor fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.debitorRel.holderPerson["`**parentServer.bookingItem.debitor.debitorRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:OWNER[[parentServer.bookingItem.debitor.debitorRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:ADMIN[[parentServer.bookingItem.debitor.debitorRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:REFERRER[[parentServer.bookingItem.debitor.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel.contact["`**bookingItem.debitor.partnerRel.contact**`"] + direction TB + style bookingItem.debitor.partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.contact:roles[ ] + style bookingItem.debitor.partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.contact:OWNER[[bookingItem.debitor.partnerRel.contact:OWNER]] + role:bookingItem.debitor.partnerRel.contact:ADMIN[[bookingItem.debitor.partnerRel.contact:ADMIN]] + role:bookingItem.debitor.partnerRel.contact:REFERRER[[bookingItem.debitor.partnerRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel["`**parentServer.bookingItem.debitorRel**`"] + direction TB + style parentServer.bookingItem.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel:roles[ ] + style parentServer.bookingItem.debitorRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel:OWNER[[parentServer.bookingItem.debitorRel:OWNER]] + role:parentServer.bookingItem.debitorRel:ADMIN[[parentServer.bookingItem.debitorRel:ADMIN]] + role:parentServer.bookingItem.debitorRel:AGENT[[parentServer.bookingItem.debitorRel:AGENT]] + role:parentServer.bookingItem.debitorRel:TENANT[[parentServer.bookingItem.debitorRel:TENANT]] + end +end + +subgraph bookingItem["`**bookingItem**`"] + direction TB + style bookingItem fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem:roles[ ] + style bookingItem:roles fill:#99bcdb,stroke:white + + role:bookingItem:OWNER[[bookingItem:OWNER]] + role:bookingItem:ADMIN[[bookingItem:ADMIN]] + role:bookingItem:AGENT[[bookingItem:AGENT]] + role:bookingItem:TENANT[[bookingItem:TENANT]] + end +end + +subgraph parentServer.parentServer["`**parentServer.parentServer**`"] + direction TB + style parentServer.parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.debitorRel.contact["`**parentServer.bookingItem.debitor.debitorRel.contact**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.contact:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.contact:OWNER[[parentServer.bookingItem.debitor.debitorRel.contact:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.contact:ADMIN[[parentServer.bookingItem.debitor.debitorRel.contact:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.contact:REFERRER[[parentServer.bookingItem.debitor.debitorRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel.holderPerson["`**bookingItem.debitor.partnerRel.holderPerson**`"] + direction TB + style bookingItem.debitor.partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.holderPerson:roles[ ] + style bookingItem.debitor.partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.holderPerson:OWNER[[bookingItem.debitor.partnerRel.holderPerson:OWNER]] + role:bookingItem.debitor.partnerRel.holderPerson:ADMIN[[bookingItem.debitor.partnerRel.holderPerson:ADMIN]] + role:bookingItem.debitor.partnerRel.holderPerson:REFERRER[[bookingItem.debitor.partnerRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitorRel.contact["`**bookingItem.debitorRel.contact**`"] + direction TB + style bookingItem.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.contact:roles[ ] + style bookingItem.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.contact:OWNER[[bookingItem.debitorRel.contact:OWNER]] + role:bookingItem.debitorRel.contact:ADMIN[[bookingItem.debitorRel.contact:ADMIN]] + role:bookingItem.debitorRel.contact:REFERRER[[bookingItem.debitorRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.refundBankAccount["`**parentServer.bookingItem.debitor.refundBankAccount**`"] + direction TB + style parentServer.bookingItem.debitor.refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.refundBankAccount:roles[ ] + style parentServer.bookingItem.debitor.refundBankAccount:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.refundBankAccount:OWNER[[parentServer.bookingItem.debitor.refundBankAccount:OWNER]] + role:parentServer.bookingItem.debitor.refundBankAccount:ADMIN[[parentServer.bookingItem.debitor.refundBankAccount:ADMIN]] + role:parentServer.bookingItem.debitor.refundBankAccount:REFERRER[[parentServer.bookingItem.debitor.refundBankAccount:REFERRER]] + end +end + +subgraph bookingItem.debitor["`**bookingItem.debitor**`"] + direction TB + style bookingItem.debitor fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph bookingItem.debitor.debitorRel.holderPerson["`**bookingItem.debitor.debitorRel.holderPerson**`"] + direction TB + style bookingItem.debitor.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.holderPerson:roles[ ] + style bookingItem.debitor.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.holderPerson:OWNER[[bookingItem.debitor.debitorRel.holderPerson:OWNER]] + role:bookingItem.debitor.debitorRel.holderPerson:ADMIN[[bookingItem.debitor.debitorRel.holderPerson:ADMIN]] + role:bookingItem.debitor.debitorRel.holderPerson:REFERRER[[bookingItem.debitor.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.debitorRel["`**bookingItem.debitor.debitorRel**`"] + direction TB + style bookingItem.debitor.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel:roles[ ] + style bookingItem.debitor.debitorRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel:OWNER[[bookingItem.debitor.debitorRel:OWNER]] + role:bookingItem.debitor.debitorRel:ADMIN[[bookingItem.debitor.debitorRel:ADMIN]] + role:bookingItem.debitor.debitorRel:AGENT[[bookingItem.debitor.debitorRel:AGENT]] + role:bookingItem.debitor.debitorRel:TENANT[[bookingItem.debitor.debitorRel:TENANT]] + end +end + +subgraph asset["`**asset**`"] + direction TB + style asset fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph asset:roles[ ] + style asset:roles fill:#dd4901,stroke:white + + role:asset:OWNER[[asset:OWNER]] + role:asset:ADMIN[[asset:ADMIN]] + role:asset:TENANT[[asset:TENANT]] + end + + subgraph asset:permissions[ ] + style asset:permissions fill:#dd4901,stroke:white + + perm:asset:INSERT{{asset:INSERT}} + perm:asset:DELETE{{asset:DELETE}} + perm:asset:UPDATE{{asset:UPDATE}} + perm:asset:SELECT{{asset:SELECT}} + end +end + +subgraph parentServer.bookingItem.debitor.debitorRel.anchorPerson["`**parentServer.bookingItem.debitor.debitorRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:OWNER[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:REFERRER]] + end +end + +%% granting roles to roles +role:global:ADMIN -.-> role:bookingItem.debitor.refundBankAccount:OWNER +role:bookingItem.debitor.refundBankAccount:OWNER -.-> role:bookingItem.debitor.refundBankAccount:ADMIN +role:bookingItem.debitor.refundBankAccount:ADMIN -.-> role:bookingItem.debitor.refundBankAccount:REFERRER +role:bookingItem.debitor.refundBankAccount:ADMIN -.-> role:bookingItem.debitor.debitorRel:AGENT +role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.refundBankAccount:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitor.partnerRel:OWNER +role:bookingItem.debitor.partnerRel:OWNER -.-> role:bookingItem.debitor.partnerRel:ADMIN +role:bookingItem.debitor.partnerRel:ADMIN -.-> role:bookingItem.debitor.partnerRel:AGENT +role:bookingItem.debitor.partnerRel:AGENT -.-> role:bookingItem.debitor.partnerRel:TENANT +role:bookingItem.debitor.partnerRel:ADMIN -.-> role:bookingItem.debitor.debitorRel:ADMIN +role:bookingItem.debitor.partnerRel:AGENT -.-> role:bookingItem.debitor.debitorRel:AGENT +role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.partnerRel:TENANT +role:global:ADMIN -.-> role:bookingItem.debitorRel.anchorPerson:OWNER +role:bookingItem.debitorRel.anchorPerson:OWNER -.-> role:bookingItem.debitorRel.anchorPerson:ADMIN +role:bookingItem.debitorRel.anchorPerson:ADMIN -.-> role:bookingItem.debitorRel.anchorPerson:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitorRel.holderPerson:OWNER +role:bookingItem.debitorRel.holderPerson:OWNER -.-> role:bookingItem.debitorRel.holderPerson:ADMIN +role:bookingItem.debitorRel.holderPerson:ADMIN -.-> role:bookingItem.debitorRel.holderPerson:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitorRel.contact:OWNER +role:bookingItem.debitorRel.contact:OWNER -.-> role:bookingItem.debitorRel.contact:ADMIN +role:bookingItem.debitorRel.contact:ADMIN -.-> role:bookingItem.debitorRel.contact:REFERRER +role:bookingItem.debitorRel:AGENT -.-> role:bookingItem:OWNER +role:bookingItem:OWNER -.-> role:bookingItem:ADMIN +role:bookingItem.debitorRel:AGENT -.-> role:bookingItem:ADMIN +role:bookingItem:ADMIN -.-> role:bookingItem:AGENT +role:bookingItem:AGENT -.-> role:bookingItem:TENANT +role:bookingItem:TENANT -.-> role:bookingItem.debitorRel:TENANT +role:bookingItem:ADMIN ==> role:asset:OWNER +role:asset:OWNER ==> role:asset:ADMIN +role:asset:ADMIN ==> role:asset:TENANT +role:asset:TENANT ==> role:bookingItem:TENANT + +%% granting permissions to roles +role:bookingItem:AGENT ==> perm:asset:INSERT +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-MANAGED_WEBSPACE.md b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md new file mode 100644 index 00000000..1b01c8ff --- /dev/null +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md @@ -0,0 +1,468 @@ +### rbac asset inCaseOf:MANAGED_WEBSPACE + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph parentServer.bookingItem["`**parentServer.bookingItem**`"] + direction TB + style parentServer.bookingItem fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem:roles[ ] + style parentServer.bookingItem:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem:OWNER[[parentServer.bookingItem:OWNER]] + role:parentServer.bookingItem:ADMIN[[parentServer.bookingItem:ADMIN]] + role:parentServer.bookingItem:AGENT[[parentServer.bookingItem:AGENT]] + role:parentServer.bookingItem:TENANT[[parentServer.bookingItem:TENANT]] + end +end + +subgraph parentServer.bookingItem.debitorRel.anchorPerson["`**parentServer.bookingItem.debitorRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.anchorPerson:OWNER[[parentServer.bookingItem.debitorRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitorRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitorRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitorRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel.holderPerson["`**parentServer.bookingItem.debitorRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.holderPerson:OWNER[[parentServer.bookingItem.debitorRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitorRel.holderPerson:ADMIN[[parentServer.bookingItem.debitorRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitorRel.holderPerson:REFERRER[[parentServer.bookingItem.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph parentServer["`**parentServer**`"] + direction TB + style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson["`**parentServer.bookingItem.debitor.partnerRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:OWNER[[parentServer.bookingItem.debitor.partnerRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:ADMIN[[parentServer.bookingItem.debitor.partnerRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.holderPerson:REFERRER[[parentServer.bookingItem.debitor.partnerRel.holderPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel.anchorPerson["`**parentServer.bookingItem.debitor.partnerRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:OWNER[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitor.partnerRel.anchorPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.debitorRel.anchorPerson["`**bookingItem.debitor.debitorRel.anchorPerson**`"] + direction TB + style bookingItem.debitor.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.anchorPerson:roles[ ] + style bookingItem.debitor.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.anchorPerson:OWNER[[bookingItem.debitor.debitorRel.anchorPerson:OWNER]] + role:bookingItem.debitor.debitorRel.anchorPerson:ADMIN[[bookingItem.debitor.debitorRel.anchorPerson:ADMIN]] + role:bookingItem.debitor.debitorRel.anchorPerson:REFERRER[[bookingItem.debitor.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel.contact["`**parentServer.bookingItem.debitorRel.contact**`"] + direction TB + style parentServer.bookingItem.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel.contact:roles[ ] + style parentServer.bookingItem.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel.contact:OWNER[[parentServer.bookingItem.debitorRel.contact:OWNER]] + role:parentServer.bookingItem.debitorRel.contact:ADMIN[[parentServer.bookingItem.debitorRel.contact:ADMIN]] + role:parentServer.bookingItem.debitorRel.contact:REFERRER[[parentServer.bookingItem.debitorRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel["`**bookingItem.debitor.partnerRel**`"] + direction TB + style bookingItem.debitor.partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel:roles[ ] + style bookingItem.debitor.partnerRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel:OWNER[[bookingItem.debitor.partnerRel:OWNER]] + role:bookingItem.debitor.partnerRel:ADMIN[[bookingItem.debitor.partnerRel:ADMIN]] + role:bookingItem.debitor.partnerRel:AGENT[[bookingItem.debitor.partnerRel:AGENT]] + role:bookingItem.debitor.partnerRel:TENANT[[bookingItem.debitor.partnerRel:TENANT]] + end +end + +subgraph bookingItem.debitor.partnerRel.anchorPerson["`**bookingItem.debitor.partnerRel.anchorPerson**`"] + direction TB + style bookingItem.debitor.partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.anchorPerson:roles[ ] + style bookingItem.debitor.partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.anchorPerson:OWNER[[bookingItem.debitor.partnerRel.anchorPerson:OWNER]] + role:bookingItem.debitor.partnerRel.anchorPerson:ADMIN[[bookingItem.debitor.partnerRel.anchorPerson:ADMIN]] + role:bookingItem.debitor.partnerRel.anchorPerson:REFERRER[[bookingItem.debitor.partnerRel.anchorPerson:REFERRER]] + end +end + +subgraph bookingItem.debitorRel["`**bookingItem.debitorRel**`"] + direction TB + style bookingItem.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel:roles[ ] + style bookingItem.debitorRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel:OWNER[[bookingItem.debitorRel:OWNER]] + role:bookingItem.debitorRel:ADMIN[[bookingItem.debitorRel:ADMIN]] + role:bookingItem.debitorRel:AGENT[[bookingItem.debitorRel:AGENT]] + role:bookingItem.debitorRel:TENANT[[bookingItem.debitorRel:TENANT]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel.contact["`**parentServer.bookingItem.debitor.partnerRel.contact**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel.contact:roles[ ] + style parentServer.bookingItem.debitor.partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel.contact:OWNER[[parentServer.bookingItem.debitor.partnerRel.contact:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel.contact:ADMIN[[parentServer.bookingItem.debitor.partnerRel.contact:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel.contact:REFERRER[[parentServer.bookingItem.debitor.partnerRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitorRel.anchorPerson["`**bookingItem.debitorRel.anchorPerson**`"] + direction TB + style bookingItem.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.anchorPerson:roles[ ] + style bookingItem.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.anchorPerson:OWNER[[bookingItem.debitorRel.anchorPerson:OWNER]] + role:bookingItem.debitorRel.anchorPerson:ADMIN[[bookingItem.debitorRel.anchorPerson:ADMIN]] + role:bookingItem.debitorRel.anchorPerson:REFERRER[[bookingItem.debitorRel.anchorPerson:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.debitorRel["`**parentServer.bookingItem.debitor.debitorRel**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel:roles[ ] + style parentServer.bookingItem.debitor.debitorRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel:OWNER[[parentServer.bookingItem.debitor.debitorRel:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel:ADMIN[[parentServer.bookingItem.debitor.debitorRel:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel:AGENT[[parentServer.bookingItem.debitor.debitorRel:AGENT]] + role:parentServer.bookingItem.debitor.debitorRel:TENANT[[parentServer.bookingItem.debitor.debitorRel:TENANT]] + end +end + +subgraph bookingItem.debitorRel.holderPerson["`**bookingItem.debitorRel.holderPerson**`"] + direction TB + style bookingItem.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.holderPerson:roles[ ] + style bookingItem.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.holderPerson:OWNER[[bookingItem.debitorRel.holderPerson:OWNER]] + role:bookingItem.debitorRel.holderPerson:ADMIN[[bookingItem.debitorRel.holderPerson:ADMIN]] + role:bookingItem.debitorRel.holderPerson:REFERRER[[bookingItem.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.refundBankAccount["`**bookingItem.debitor.refundBankAccount**`"] + direction TB + style bookingItem.debitor.refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.refundBankAccount:roles[ ] + style bookingItem.debitor.refundBankAccount:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.refundBankAccount:OWNER[[bookingItem.debitor.refundBankAccount:OWNER]] + role:bookingItem.debitor.refundBankAccount:ADMIN[[bookingItem.debitor.refundBankAccount:ADMIN]] + role:bookingItem.debitor.refundBankAccount:REFERRER[[bookingItem.debitor.refundBankAccount:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.partnerRel["`**parentServer.bookingItem.debitor.partnerRel**`"] + direction TB + style parentServer.bookingItem.debitor.partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.partnerRel:roles[ ] + style parentServer.bookingItem.debitor.partnerRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.partnerRel:OWNER[[parentServer.bookingItem.debitor.partnerRel:OWNER]] + role:parentServer.bookingItem.debitor.partnerRel:ADMIN[[parentServer.bookingItem.debitor.partnerRel:ADMIN]] + role:parentServer.bookingItem.debitor.partnerRel:AGENT[[parentServer.bookingItem.debitor.partnerRel:AGENT]] + role:parentServer.bookingItem.debitor.partnerRel:TENANT[[parentServer.bookingItem.debitor.partnerRel:TENANT]] + end +end + +subgraph bookingItem.debitor.debitorRel.contact["`**bookingItem.debitor.debitorRel.contact**`"] + direction TB + style bookingItem.debitor.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.contact:roles[ ] + style bookingItem.debitor.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.contact:OWNER[[bookingItem.debitor.debitorRel.contact:OWNER]] + role:bookingItem.debitor.debitorRel.contact:ADMIN[[bookingItem.debitor.debitorRel.contact:ADMIN]] + role:bookingItem.debitor.debitorRel.contact:REFERRER[[bookingItem.debitor.debitorRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor["`**parentServer.bookingItem.debitor**`"] + direction TB + style parentServer.bookingItem.debitor fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.debitorRel.holderPerson["`**parentServer.bookingItem.debitor.debitorRel.holderPerson**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.holderPerson:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:OWNER[[parentServer.bookingItem.debitor.debitorRel.holderPerson:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:ADMIN[[parentServer.bookingItem.debitor.debitorRel.holderPerson:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.holderPerson:REFERRER[[parentServer.bookingItem.debitor.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel.contact["`**bookingItem.debitor.partnerRel.contact**`"] + direction TB + style bookingItem.debitor.partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.contact:roles[ ] + style bookingItem.debitor.partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.contact:OWNER[[bookingItem.debitor.partnerRel.contact:OWNER]] + role:bookingItem.debitor.partnerRel.contact:ADMIN[[bookingItem.debitor.partnerRel.contact:ADMIN]] + role:bookingItem.debitor.partnerRel.contact:REFERRER[[bookingItem.debitor.partnerRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitorRel["`**parentServer.bookingItem.debitorRel**`"] + direction TB + style parentServer.bookingItem.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitorRel:roles[ ] + style parentServer.bookingItem.debitorRel:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitorRel:OWNER[[parentServer.bookingItem.debitorRel:OWNER]] + role:parentServer.bookingItem.debitorRel:ADMIN[[parentServer.bookingItem.debitorRel:ADMIN]] + role:parentServer.bookingItem.debitorRel:AGENT[[parentServer.bookingItem.debitorRel:AGENT]] + role:parentServer.bookingItem.debitorRel:TENANT[[parentServer.bookingItem.debitorRel:TENANT]] + end +end + +subgraph bookingItem["`**bookingItem**`"] + direction TB + style bookingItem fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem:roles[ ] + style bookingItem:roles fill:#99bcdb,stroke:white + + role:bookingItem:OWNER[[bookingItem:OWNER]] + role:bookingItem:ADMIN[[bookingItem:ADMIN]] + role:bookingItem:AGENT[[bookingItem:AGENT]] + role:bookingItem:TENANT[[bookingItem:TENANT]] + end +end + +subgraph parentServer.parentServer["`**parentServer.parentServer**`"] + direction TB + style parentServer.parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph parentServer.bookingItem.debitor.debitorRel.contact["`**parentServer.bookingItem.debitor.debitorRel.contact**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.contact:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.contact:OWNER[[parentServer.bookingItem.debitor.debitorRel.contact:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.contact:ADMIN[[parentServer.bookingItem.debitor.debitorRel.contact:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.contact:REFERRER[[parentServer.bookingItem.debitor.debitorRel.contact:REFERRER]] + end +end + +subgraph bookingItem.debitor.partnerRel.holderPerson["`**bookingItem.debitor.partnerRel.holderPerson**`"] + direction TB + style bookingItem.debitor.partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.partnerRel.holderPerson:roles[ ] + style bookingItem.debitor.partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.partnerRel.holderPerson:OWNER[[bookingItem.debitor.partnerRel.holderPerson:OWNER]] + role:bookingItem.debitor.partnerRel.holderPerson:ADMIN[[bookingItem.debitor.partnerRel.holderPerson:ADMIN]] + role:bookingItem.debitor.partnerRel.holderPerson:REFERRER[[bookingItem.debitor.partnerRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitorRel.contact["`**bookingItem.debitorRel.contact**`"] + direction TB + style bookingItem.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitorRel.contact:roles[ ] + style bookingItem.debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitorRel.contact:OWNER[[bookingItem.debitorRel.contact:OWNER]] + role:bookingItem.debitorRel.contact:ADMIN[[bookingItem.debitorRel.contact:ADMIN]] + role:bookingItem.debitorRel.contact:REFERRER[[bookingItem.debitorRel.contact:REFERRER]] + end +end + +subgraph parentServer.bookingItem.debitor.refundBankAccount["`**parentServer.bookingItem.debitor.refundBankAccount**`"] + direction TB + style parentServer.bookingItem.debitor.refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.refundBankAccount:roles[ ] + style parentServer.bookingItem.debitor.refundBankAccount:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.refundBankAccount:OWNER[[parentServer.bookingItem.debitor.refundBankAccount:OWNER]] + role:parentServer.bookingItem.debitor.refundBankAccount:ADMIN[[parentServer.bookingItem.debitor.refundBankAccount:ADMIN]] + role:parentServer.bookingItem.debitor.refundBankAccount:REFERRER[[parentServer.bookingItem.debitor.refundBankAccount:REFERRER]] + end +end + +subgraph bookingItem.debitor["`**bookingItem.debitor**`"] + direction TB + style bookingItem.debitor fill:#99bcdb,stroke:#274d6e,stroke-width:8px +end + +subgraph bookingItem.debitor.debitorRel.holderPerson["`**bookingItem.debitor.debitorRel.holderPerson**`"] + direction TB + style bookingItem.debitor.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel.holderPerson:roles[ ] + style bookingItem.debitor.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel.holderPerson:OWNER[[bookingItem.debitor.debitorRel.holderPerson:OWNER]] + role:bookingItem.debitor.debitorRel.holderPerson:ADMIN[[bookingItem.debitor.debitorRel.holderPerson:ADMIN]] + role:bookingItem.debitor.debitorRel.holderPerson:REFERRER[[bookingItem.debitor.debitorRel.holderPerson:REFERRER]] + end +end + +subgraph bookingItem.debitor.debitorRel["`**bookingItem.debitor.debitorRel**`"] + direction TB + style bookingItem.debitor.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem.debitor.debitorRel:roles[ ] + style bookingItem.debitor.debitorRel:roles fill:#99bcdb,stroke:white + + role:bookingItem.debitor.debitorRel:OWNER[[bookingItem.debitor.debitorRel:OWNER]] + role:bookingItem.debitor.debitorRel:ADMIN[[bookingItem.debitor.debitorRel:ADMIN]] + role:bookingItem.debitor.debitorRel:AGENT[[bookingItem.debitor.debitorRel:AGENT]] + role:bookingItem.debitor.debitorRel:TENANT[[bookingItem.debitor.debitorRel:TENANT]] + end +end + +subgraph asset["`**asset**`"] + direction TB + style asset fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph asset:roles[ ] + style asset:roles fill:#dd4901,stroke:white + + role:asset:OWNER[[asset:OWNER]] + role:asset:ADMIN[[asset:ADMIN]] + role:asset:TENANT[[asset:TENANT]] + end + + subgraph asset:permissions[ ] + style asset:permissions fill:#dd4901,stroke:white + + perm:asset:INSERT{{asset:INSERT}} + perm:asset:DELETE{{asset:DELETE}} + perm:asset:UPDATE{{asset:UPDATE}} + perm:asset:SELECT{{asset:SELECT}} + end +end + +subgraph parentServer.bookingItem.debitor.debitorRel.anchorPerson["`**parentServer.bookingItem.debitor.debitorRel.anchorPerson**`"] + direction TB + style parentServer.bookingItem.debitor.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentServer.bookingItem.debitor.debitorRel.anchorPerson:roles[ ] + style parentServer.bookingItem.debitor.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:OWNER[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:OWNER]] + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:ADMIN[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:ADMIN]] + role:parentServer.bookingItem.debitor.debitorRel.anchorPerson:REFERRER[[parentServer.bookingItem.debitor.debitorRel.anchorPerson:REFERRER]] + end +end + +%% granting roles to roles +role:global:ADMIN -.-> role:bookingItem.debitor.refundBankAccount:OWNER +role:bookingItem.debitor.refundBankAccount:OWNER -.-> role:bookingItem.debitor.refundBankAccount:ADMIN +role:bookingItem.debitor.refundBankAccount:ADMIN -.-> role:bookingItem.debitor.refundBankAccount:REFERRER +role:bookingItem.debitor.refundBankAccount:ADMIN -.-> role:bookingItem.debitor.debitorRel:AGENT +role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.refundBankAccount:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitor.partnerRel:OWNER +role:bookingItem.debitor.partnerRel:OWNER -.-> role:bookingItem.debitor.partnerRel:ADMIN +role:bookingItem.debitor.partnerRel:ADMIN -.-> role:bookingItem.debitor.partnerRel:AGENT +role:bookingItem.debitor.partnerRel:AGENT -.-> role:bookingItem.debitor.partnerRel:TENANT +role:bookingItem.debitor.partnerRel:ADMIN -.-> role:bookingItem.debitor.debitorRel:ADMIN +role:bookingItem.debitor.partnerRel:AGENT -.-> role:bookingItem.debitor.debitorRel:AGENT +role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.partnerRel:TENANT +role:global:ADMIN -.-> role:bookingItem.debitorRel.anchorPerson:OWNER +role:bookingItem.debitorRel.anchorPerson:OWNER -.-> role:bookingItem.debitorRel.anchorPerson:ADMIN +role:bookingItem.debitorRel.anchorPerson:ADMIN -.-> role:bookingItem.debitorRel.anchorPerson:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitorRel.holderPerson:OWNER +role:bookingItem.debitorRel.holderPerson:OWNER -.-> role:bookingItem.debitorRel.holderPerson:ADMIN +role:bookingItem.debitorRel.holderPerson:ADMIN -.-> role:bookingItem.debitorRel.holderPerson:REFERRER +role:global:ADMIN -.-> role:bookingItem.debitorRel.contact:OWNER +role:bookingItem.debitorRel.contact:OWNER -.-> role:bookingItem.debitorRel.contact:ADMIN +role:bookingItem.debitorRel.contact:ADMIN -.-> role:bookingItem.debitorRel.contact:REFERRER +role:bookingItem.debitorRel:AGENT -.-> role:bookingItem:OWNER +role:bookingItem:OWNER -.-> role:bookingItem:ADMIN +role:bookingItem.debitorRel:AGENT -.-> role:bookingItem:ADMIN +role:bookingItem:ADMIN -.-> role:bookingItem:AGENT +role:bookingItem:AGENT -.-> role:bookingItem:TENANT +role:bookingItem:TENANT -.-> role:bookingItem.debitorRel:TENANT +role:parentServer.bookingItem.debitorRel:AGENT -.-> role:parentServer.bookingItem:OWNER +role:parentServer.bookingItem:OWNER -.-> role:parentServer.bookingItem:ADMIN +role:parentServer.bookingItem.debitorRel:AGENT -.-> role:parentServer.bookingItem:ADMIN +role:parentServer.bookingItem:ADMIN -.-> role:parentServer.bookingItem:AGENT +role:parentServer.bookingItem:AGENT -.-> role:parentServer.bookingItem:TENANT +role:parentServer.bookingItem:TENANT -.-> role:parentServer.bookingItem.debitorRel:TENANT +role:bookingItem:ADMIN ==> role:asset:OWNER +role:asset:OWNER ==> role:asset:ADMIN +role:asset:ADMIN ==> role:asset:TENANT +role:asset:TENANT ==> role:bookingItem:TENANT + +%% granting permissions to roles +role:bookingItem:AGENT ==> perm:asset:INSERT +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 new file mode 100644 index 00000000..bc6939db --- /dev/null +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql @@ -0,0 +1,180 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-hosting-asset-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_hosting_asset'); +--// + + +-- ============================================================================ +--changeset hs-hosting-asset-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsHostingAsset', 'hs_hosting_asset'); +--// + + +-- ============================================================================ +--changeset hs-hosting-asset-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsHostingAsset( + NEW hs_hosting_asset +) + language plpgsql as $$ + +declare + newParentServer hs_hosting_asset; + newBookingItem hs_booking_item; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT * FROM hs_hosting_asset WHERE uuid = NEW.parentAssetUuid INTO newParentServer; + + SELECT * FROM hs_booking_item WHERE uuid = NEW.bookingItemUuid INTO newBookingItem; + assert newBookingItem.uuid is not null, format('newBookingItem must not be null for NEW.bookingItemUuid = %s', NEW.bookingItemUuid); + + + perform createRoleWithGrants( + hsHostingAssetOWNER(NEW), + permissions => array['DELETE'], + incomingSuperRoles => array[hsBookingItemADMIN(newBookingItem)] + ); + + perform createRoleWithGrants( + hsHostingAssetADMIN(NEW), + permissions => array['UPDATE'], + incomingSuperRoles => array[hsHostingAssetOWNER(NEW)] + ); + + perform createRoleWithGrants( + hsHostingAssetTENANT(NEW), + permissions => array['SELECT'], + incomingSuperRoles => array[hsHostingAssetADMIN(NEW)], + outgoingSubRoles => array[hsBookingItemTENANT(newBookingItem)] + ); + + IF NEW.type = 'CLOUD_SERVER' THEN + ELSIF NEW.type = 'MANAGED_SERVER' THEN + ELSIF NEW.type = 'MANAGED_WEBSPACE' THEN + END IF; + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_hosting_asset row. + */ + +create or replace function insertTriggerForHsHostingAsset_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsHostingAsset(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsHostingAsset_tg + after insert on hs_hosting_asset + for each row +execute procedure insertTriggerForHsHostingAsset_tf(); +--// + + +-- ============================================================================ +--changeset hs-hosting-asset-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates INSERT INTO hs_hosting_asset permissions for the related hs_booking_item rows. + */ +do language plpgsql $$ + declare + row hs_booking_item; + begin + call defineContext('create INSERT INTO hs_hosting_asset permissions for the related hs_booking_item rows'); + + FOR row IN SELECT * FROM hs_booking_item + LOOP + call grantPermissionToRole( + createPermission(row.uuid, 'INSERT', 'hs_hosting_asset'), + hsBookingItemAGENT(row)); + END LOOP; + END; +$$; + +/** + Adds hs_hosting_asset INSERT permission to specified role of new hs_booking_item rows. +*/ +create or replace function hs_hosting_asset_hs_booking_item_insert_tf() + returns trigger + language plpgsql + strict as $$ +begin + call grantPermissionToRole( + createPermission(NEW.uuid, 'INSERT', 'hs_hosting_asset'), + hsBookingItemAGENT(NEW)); + return NEW; +end; $$; + +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_hs_hosting_asset_hs_booking_item_insert_tg + after insert on hs_booking_item + for each row +execute procedure hs_hosting_asset_hs_booking_item_insert_tf(); + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_hosting_asset, + where the check is performed by a direct role. + + A direct role is a role depending on a foreign key directly available in the NEW row. +*/ +create or replace function hs_hosting_asset_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_hosting_asset not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_hosting_asset_insert_permission_check_tg + before insert on hs_hosting_asset + for each row + when ( not hasInsertPermission(NEW.bookingItemUuid, 'INSERT', 'hs_hosting_asset') ) + execute procedure hs_hosting_asset_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-hosting-asset-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + + call generateRbacIdentityViewFromQuery('hs_hosting_asset', + $idName$ + SELECT asset.uuid as uuid, bookingItemIV.idName || '-' || cleanIdentifier(asset.identifier) as idName + FROM hs_hosting_asset asset + JOIN hs_booking_item_iv bookingItemIV ON bookingItemIV.uuid = asset.bookingItemUuid + $idName$); +--// + +-- ============================================================================ +--changeset hs-hosting-asset-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_hosting_asset', + $orderBy$ + identifier + $orderBy$, + $updates$ + version = new.version, + caption = new.caption, + config = new.config + $updates$); +--// + 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 new file mode 100644 index 00000000..1e840acd --- /dev/null +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql @@ -0,0 +1,58 @@ +--liquibase formatted sql + + +-- ============================================================================ +--changeset hs-hosting-asset-TEST-DATA-GENERATOR:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates a single hs_hosting_asset test record. + */ +create or replace procedure createHsHostingAssetTestData( + givenPartnerNumber numeric, + givenDebitorSuffix char(2), + givenWebspacePrefix char(3) + ) + language plpgsql as $$ +declare + currentTask varchar; + relatedDebitor hs_office_debitor; + relatedBookingItem hs_booking_item; +begin + currentTask := 'creating hosting-asset test-data ' || givenPartnerNumber::text || givenDebitorSuffix; + call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); + execute format('set local hsadminng.currentTask to %L', currentTask); + + select debitor.* into relatedDebitor + from hs_office_debitor debitor + join hs_office_relation debitorRel on debitorRel.uuid = debitor.debitorRelUuid + join hs_office_relation partnerRel on partnerRel.holderUuid = debitorRel.anchorUuid + join hs_office_partner partner on partner.partnerRelUuid = partnerRel.uuid + where partner.partnerNumber = givenPartnerNumber and debitor.debitorNumberSuffix = givenDebitorSuffix; + select item.* into relatedBookingItem + from hs_booking_item item + where item.debitoruuid = relatedDebitor.uuid + and item.caption = 'some PrivateCloud'; + + raise notice 'creating test hosting-asset: %', givenPartnerNumber::text || givenDebitorSuffix::text; + raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor; + insert + into hs_hosting_asset (uuid, bookingitemuuid, type, identifier, caption, config) + values (uuid_generate_v4(), relatedBookingItem.uuid, 'MANAGED_SERVER'::HsHostingAssetType, 'vm10' || givenDebitorSuffix, 'some ManagedServer', '{ "CPU": 2, "SDD": 512, "extra": 42 }'::jsonb), + (uuid_generate_v4(), relatedBookingItem.uuid, 'CLOUD_SERVER'::HsHostingAssetType, 'vm20' || givenDebitorSuffix, 'another CloudServer', '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb), + (uuid_generate_v4(), relatedBookingItem.uuid, 'MANAGED_WEBSPACE'::HsHostingAssetType, givenWebspacePrefix || '01', 'some Webspace', '{ "RAM": 1, "SDD": 512, "HDD": 2048, "extra": 42 }'::jsonb); +end; $$; +--// + + +-- ============================================================================ +--changeset hs-hosting-asset-TEST-DATA-GENERATION:1 –context=dev,tc endDelimiter:--// +-- ---------------------------------------------------------------------------- + +do language plpgsql $$ + begin + call createHsHostingAssetTestData(10001, '11', 'aaa'); + call createHsHostingAssetTestData(10002, '12', 'bbb'); + call createHsHostingAssetTestData(10003, '13', 'ccc'); + end; +$$; diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index fccafed2..7be8f944 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -133,3 +133,9 @@ databaseChangeLog: file: db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.sql - include: file: db/changelog/6-hs-booking/601-booking-item/6018-hs-booking-item-test-data.sql + - include: + file: db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql + - include: + file: db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql + - include: + file: db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql diff --git a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java index 1523f7cf..15f9c152 100644 --- a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java +++ b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java @@ -51,6 +51,7 @@ public class ArchitectureTest { "..hs.office.relation", "..hs.office.sepamandate", "..hs.booking.item", + "..hs.hosting.asset", "..errors", "..mapper", "..ping", @@ -130,6 +131,7 @@ public class ArchitectureTest { .resideInAnyPackage( "..hs.office.(*)..", "..hs.booking.(*)..", + "..hs.hosting.(*)..", "..rbac.rbacgrant" // TODO.test: just because of RbacGrantsDiagramServiceIntegrationTest ); @@ -140,7 +142,16 @@ public class ArchitectureTest { .should().onlyBeAccessed().byClassesThat() .resideInAnyPackage( "..hs.booking.(*)..", - "..rbac.rbacgrant" // TODO.test: just because of RbacGrantsDiagramServiceIntegrationTest + "..hs.hosting.(*).." + ); + + @ArchTest + @SuppressWarnings("unused") + public static final ArchRule hsHostingPackageAccessRule = classes() + .that().resideInAPackage("..hs.hosting.(*)..") + .should().onlyBeAccessed().byClassesThat() + .resideInAnyPackage( + "..hs.hosting.(*).." ); @ArchTest diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index 7dc92ebd..7bebdcbb 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -118,17 +118,16 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup .containsExactlyInAnyOrder(fromFormatted( initialGrantNames, - // insert+delete + // global-admin "{ grant perm:hs_booking_item#D-1000111-somenewbookingitem:DELETE to role:global#global:ADMIN by system and assume }", // owner - //"{ grant perm:hs_booking_item#D-1000111-somenewbookingitem:UPDATE to role:hs_booking_item#D-1000111-somenewbookingitem:OWNER by system and assume }", "{ grant role:hs_booking_item#D-1000111-somenewbookingitem:OWNER to role:relation#FirstGmbH-with-DEBITOR-FirstGmbH:AGENT by system and assume }", // admin "{ grant perm:hs_booking_item#D-1000111-somenewbookingitem:UPDATE to role:hs_booking_item#D-1000111-somenewbookingitem:ADMIN by system and assume }", "{ grant role:hs_booking_item#D-1000111-somenewbookingitem:ADMIN to role:hs_booking_item#D-1000111-somenewbookingitem:OWNER by system and assume }", - //"{ grant role:hs_booking_item#D-1000111-somenewbookingitem:TENANT to role:hs_booking_item#D-1000111-somenewbookingitem:ADMIN by system and assume }", + "{ grant perm:hs_booking_item#D-1000111-somenewbookingitem:INSERT>hs_hosting_asset to role:hs_booking_item#D-1000111-somenewbookingitem:AGENT by system and assume }", // agent "{ grant role:hs_booking_item#D-1000111-somenewbookingitem:ADMIN to role:relation#FirstGmbH-with-DEBITOR-FirstGmbH:AGENT by system and assume }", diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java new file mode 100644 index 00000000..1706cac4 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/TestHsBookingItem.java @@ -0,0 +1,24 @@ +package net.hostsharing.hsadminng.hs.booking.item; + +import io.hypersistence.utils.hibernate.type.range.Range; +import lombok.experimental.UtilityClass; + +import java.time.LocalDate; +import java.util.Map; + +import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR; + +@UtilityClass +public class TestHsBookingItem { + + public static final HsBookingItemEntity TEST_BOOKING_ITEM = HsBookingItemEntity.builder() + .debitor(TEST_DEBITOR) + .caption("test booking item") + .resources(Map.ofEntries( + entry("someThing", 1), + entry("anotherThing", "blue") + )) + .validity(Range.closedInfinite(LocalDate.of(2020, 1, 15))) + .build(); +} 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 new file mode 100644 index 00000000..d2c73b7c --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -0,0 +1,346 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import net.hostsharing.hsadminng.HsadminNgApplication; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository; +import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; +import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; +import net.hostsharing.hsadminng.rbac.test.JpaAttempt; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Map; +import java.util.UUID; + +import static java.util.Map.entry; +import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.matchesRegex; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = { HsadminNgApplication.class, JpaAttempt.class } +) +@Transactional +class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup { + + @LocalServerPort + private Integer port; + + @Autowired + HsHostingAssetRepository assetRepo; + + @Autowired + HsBookingItemRepository bookingItemRepo; + + @Autowired + HsOfficeDebitorRepository debitorRepo; + + @Autowired + JpaAttempt jpaAttempt; + + @Nested + class ListAssets { + + @Test + void globalAdmin_canViewAllAssetsOfArbitraryDebitor() { + + // given + context("superuser-alex@hostsharing.net"); + final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(1000111).get(0); + + RestAssured // @formatter:off + .given() + .header("current-user", "superuser-alex@hostsharing.net") + .port(port) + .when() + .get("http://localhost/api/hs/hosting/assets?debitorUuid=" + givenDebitor.getUuid()) + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + { + "type": "MANAGED_WEBSPACE", + "identifier": "aaa01", + "caption": "some Webspace", + "config": { + "HDD": 2048, + "RAM": 1, + "SDD": 512, + "extra": 42 + } + }, + { + "type": "MANAGED_SERVER", + "identifier": "vm1011", + "caption": "some ManagedServer", + "config": { + "CPU": 2, + "SDD": 512, + "extra": 42 + } + }, + { + "type": "CLOUD_SERVER", + "identifier": "vm2011", + "caption": "another CloudServer", + "config": { + "CPU": 2, + "HDD": 1024, + "extra": 42 + } + } + ] + """)); + // @formatter:on + } + } + + @Nested + class AddServer { + + @Test + void globalAdmin_canAddAsset() { + + context.define("superuser-alex@hostsharing.net"); + final var givenBookingItem = givenBookingItem("First", "some PrivateCloud"); + + final var location = RestAssured // @formatter:off + .given() + .header("current-user", "superuser-alex@hostsharing.net") + .contentType(ContentType.JSON) + .body(""" + { + "bookingItemUuid": "%s", + "type": "MANAGED_SERVER", + "identifier": "vm1400", + "caption": "some new CloudServer", + "config": { "CPU": 3, "extra": 42 } + } + """.formatted(givenBookingItem.getUuid())) + .port(port) + .when() + .post("http://localhost/api/hs/hosting/assets") + .then().log().all().assertThat() + .statusCode(201) + .contentType(ContentType.JSON) + .body("", lenientlyEquals(""" + { + "type": "MANAGED_SERVER", + "identifier": "vm1400", + "caption": "some new CloudServer", + "config": { "CPU": 3, "extra": 42 } + } + """)) + .header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/assets/[^/]*")) + .extract().header("Location"); // @formatter:on + + // finally, the new asset can be accessed under the generated UUID + final var newUserUuid = UUID.fromString( + location.substring(location.lastIndexOf('/') + 1)); + assertThat(newUserUuid).isNotNull(); + } + } + + @Nested + class GetASset { + + @Test + void globalAdmin_canGetArbitraryAsset() { + context.define("superuser-alex@hostsharing.net"); + final var givenAssetUuid = assetRepo.findAll().stream() + .filter(bi -> bi.getBookingItem().getDebitor().getDebitorNumber() == 1000111) + .filter(item -> item.getCaption().equals("some ManagedServer")) + .findAny().orElseThrow().getUuid(); + + RestAssured // @formatter:off + .given() + .header("current-user", "superuser-alex@hostsharing.net") + .port(port) + .when() + .get("http://localhost/api/hs/hosting/assets/" + givenAssetUuid) + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + { + "caption": "some ManagedServer", + "config": { + "CPU": 2, + "SDD": 512, + "extra": 42 + } + } + """)); // @formatter:on + } + + @Test + void normalUser_canNotGetUnrelatedAsset() { + context.define("superuser-alex@hostsharing.net"); + final var givenAssetUuid = assetRepo.findAll().stream() + .filter(bi -> bi.getBookingItem().getDebitor().getDebitorNumber() == 1000212) + .map(HsHostingAssetEntity::getUuid) + .findAny().orElseThrow(); + + RestAssured // @formatter:off + .given() + .header("current-user", "selfregistered-user-drew@hostsharing.org") + .port(port) + .when() + .get("http://localhost/api/hs/hosting/assets/" + givenAssetUuid) + .then().log().body().assertThat() + .statusCode(404); // @formatter:on + } + + @Test + void debitorAgentUser_canGetRelatedAsset() { + context.define("superuser-alex@hostsharing.net"); + final var givenAssetUuid = assetRepo.findAll().stream() + .filter(bi -> bi.getBookingItem().getDebitor().getDebitorNumber() == 1000313) + .filter(bi -> bi.getCaption().equals("some ManagedServer")) + .findAny().orElseThrow().getUuid(); + + RestAssured // @formatter:off + .given() + .header("current-user", "person-TuckerJack@example.com") + .port(port) + .when() + .get("http://localhost/api/hs/hosting/assets/" + givenAssetUuid) + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + { + "identifier": "vm1013", + "caption": "some ManagedServer", + "config": { + "CPU": 2, + "SDD": 512, + "extra": 42 + } + } + """)); // @formatter:on + } + } + + @Nested + class PatchAsset { + + @Test + void globalAdmin_canPatchAllUpdatablePropertiesOfAsset() { + + final var givenAsset = givenSomeTemporaryAssetForDebitorNumber("2001", entry("something", 1)); + + RestAssured // @formatter:off + .given() + .header("current-user", "superuser-alex@hostsharing.net") + .contentType(ContentType.JSON) + .body(""" + { + "config": { + "CPU": "4", + "HDD": null, + "SSD": "4096" + } + } + """) + .port(port) + .when() + .patch("http://localhost/api/hs/hosting/assets/" + givenAsset.getUuid()) + .then().log().all().assertThat() + .statusCode(200) + .contentType(ContentType.JSON) + .body("", lenientlyEquals(""" + { + "type": "CLOUD_SERVER", + "identifier": "vm2001", + "caption": "some test-asset", + "config": { + "CPU": "4", + "SSD": "4096", + "something": 1 + } + } + """)); // @formatter:on + + // finally, the asset is actually updated + 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 })"); + return true; + }); + } + } + + @Nested + class DeleteAsset { + + @Test + void globalAdmin_canDeleteArbitraryAsset() { + context.define("superuser-alex@hostsharing.net"); + final var givenAsset = givenSomeTemporaryAssetForDebitorNumber("2002", entry("something", 1)); + + RestAssured // @formatter:off + .given() + .header("current-user", "superuser-alex@hostsharing.net") + .port(port) + .when() + .delete("http://localhost/api/hs/hosting/assets/" + givenAsset.getUuid()) + .then().log().body().assertThat() + .statusCode(204); // @formatter:on + + // then the given assets is gone + assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isEmpty(); + } + + @Test + void normalUser_canNotDeleteUnrelatedAsset() { + context.define("superuser-alex@hostsharing.net"); + final var givenAsset = givenSomeTemporaryAssetForDebitorNumber("2003", entry("something", 1)); + + RestAssured // @formatter:off + .given() + .header("current-user", "selfregistered-user-drew@hostsharing.org") + .port(port) + .when() + .delete("http://localhost/api/hs/hosting/assets/" + givenAsset.getUuid()) + .then().log().body().assertThat() + .statusCode(404); // @formatter:on + + // then the given asset is still there + assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isNotEmpty(); + } + } + + HsBookingItemEntity givenBookingItem(final String debitorName, final String bookingItemCaption) { + final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).stream().findAny().orElseThrow(); + return bookingItemRepo.findAllByDebitorUuid(givenDebitor.getUuid()).stream() + .filter(i -> i.getCaption().equals(bookingItemCaption)) + .findAny().orElseThrow(); + } + + private HsHostingAssetEntity givenSomeTemporaryAssetForDebitorNumber(final String identifierSuffix, + final Map.Entry resources) { + return jpaAttempt.transacted(() -> { + context.define("superuser-alex@hostsharing.net"); + final var newAsset = HsHostingAssetEntity.builder() + .uuid(UUID.randomUUID()) + .bookingItem(givenBookingItem("First", "some CloudServer")) + .type(HsHostingAssetType.CLOUD_SERVER) + .identifier("vm" + identifierSuffix) + .caption("some test-asset") + .config(Map.ofEntries(resources)) + .build(); + + return assetRepo.save(newAsset); + }).assertSuccessful().returnedValue(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java new file mode 100644 index 00000000..d726c9b4 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java @@ -0,0 +1,102 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource; +import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; +import net.hostsharing.hsadminng.mapper.KeyValueMap; +import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import jakarta.persistence.EntityManager; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Stream; + +import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_BOOKING_ITEM; +import static net.hostsharing.hsadminng.mapper.PatchMap.entry; +import static net.hostsharing.hsadminng.mapper.PatchMap.patchMap; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; + +@TestInstance(PER_CLASS) +@ExtendWith(MockitoExtension.class) +class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< + HsHostingAssetPatchResource, + HsHostingAssetEntity + > { + + private static final UUID INITIAL_BOOKING_ITEM_UUID = UUID.randomUUID(); + + private static final Map INITIAL_CONFIG = patchMap( + entry("CPU", 1), + entry("HDD", 1024), + entry("MEM", 64) + ); + private static final Map PATCH_CONFIG = patchMap( + entry("CPU", 2), + entry("HDD", null), + entry("SDD", 256) + ); + private static final Map PATCHED_CONFIG = patchMap( + entry("CPU", 2), + entry("SDD", 256), + entry("MEM", 64) + ); + + private static final String INITIAL_CAPTION = "initial caption"; + private static final String PATCHED_CAPTION = "patched caption"; + + @Mock + private EntityManager em; + + @BeforeEach + void initMocks() { + lenient().when(em.getReference(eq(HsOfficeDebitorEntity.class), any())).thenAnswer(invocation -> + HsOfficeDebitorEntity.builder().uuid(invocation.getArgument(1)).build()); + lenient().when(em.getReference(eq(HsHostingAssetEntity.class), any())).thenAnswer(invocation -> + HsHostingAssetEntity.builder().uuid(invocation.getArgument(1)).build()); + } + + @Override + protected HsHostingAssetEntity newInitialEntity() { + final var entity = new HsHostingAssetEntity(); + entity.setUuid(INITIAL_BOOKING_ITEM_UUID); + entity.setBookingItem(TEST_BOOKING_ITEM); + entity.getConfig().putAll(KeyValueMap.from(INITIAL_CONFIG)); + entity.setCaption(INITIAL_CAPTION); + return entity; + } + + @Override + protected HsHostingAssetPatchResource newPatchResource() { + return new HsHostingAssetPatchResource(); + } + + @Override + protected HsHostingAssetEntityPatcher createPatcher(final HsHostingAssetEntity server) { + return new HsHostingAssetEntityPatcher(server); + } + + @Override + protected Stream propertyTestDescriptors() { + return Stream.of( + new JsonNullableProperty<>( + "caption", + HsHostingAssetPatchResource::setCaption, + PATCHED_CAPTION, + HsHostingAssetEntity::setCaption), + new SimpleProperty<>( + "config", + HsHostingAssetPatchResource::setConfig, + PATCH_CONFIG, + HsHostingAssetEntity::putConfig, + PATCHED_CONFIG) + .notNullable() + ); + } +} 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 new file mode 100644 index 00000000..4a878bf7 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java @@ -0,0 +1,49 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.util.Map.entry; +import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_BOOKING_ITEM; +import static org.assertj.core.api.Assertions.assertThat; + +class HsHostingAssetEntityUnitTest { + + final HsHostingAssetEntity givenParentAsset = HsHostingAssetEntity.builder() + .bookingItem(TEST_BOOKING_ITEM) + .type(HsHostingAssetType.MANAGED_SERVER) + .identifier("vm1234") + .caption("some managed asset") + .config(Map.ofEntries( + entry("CPUs", 2), + entry("SSD-storage", 512), + entry("HDD-storage", 2048))) + .build(); + final HsHostingAssetEntity givenServer = HsHostingAssetEntity.builder() + .bookingItem(TEST_BOOKING_ITEM) + .type(HsHostingAssetType.MANAGED_WEBSPACE) + .parentAsset(givenParentAsset) + .identifier("xyz00") + .caption("some managed webspace") + .config(Map.ofEntries( + entry("CPUs", 2), + entry("SSD-storage", 512), + entry("HDD-storage", 2048))) + .build(); + + @Test + void toStringContainsAllPropertiesAndResourcesSortedByKey() { + 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 })"); + } + + @Test + void toShortStringContainsOnlyMemberNumberAndCaption() { + final var result = givenServer.toShortString(); + + assertThat(result).isEqualTo("D-1000100:test booking item: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 new file mode 100644 index 00000000..3124ac39 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -0,0 +1,368 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository; +import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; +import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; +import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; +import net.hostsharing.hsadminng.rbac.test.Array; +import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; +import net.hostsharing.hsadminng.rbac.test.JpaAttempt; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.orm.jpa.JpaSystemException; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.List; +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.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; +import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; +import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted; +import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@Import({ Context.class, JpaAttempt.class }) +class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanup { + + @Autowired + HsHostingAssetRepository assetRepo; + + @Autowired + HsBookingItemRepository bookingItemRepo; + + @Autowired + HsOfficeDebitorRepository debitorRepo; + + @Autowired + RawRbacRoleRepository rawRoleRepo; + + @Autowired + RawRbacGrantRepository rawGrantRepo; + + @Autowired + JpaAttempt jpaAttempt; + + @PersistenceContext + EntityManager em; + + @MockBean + HttpServletRequest request; + + @Nested + class CreateAsset { + + @Test + public void testHostsharingAdmin_withoutAssumedRole_canCreateNewAsset() { + // given + context("superuser-alex@hostsharing.net"); + final var count = assetRepo.count(); + final var givenBookingItem = givenBookingItem("First", "some CloudServer"); + + // when + final var result = attempt(em, () -> { + final var newAsset = HsHostingAssetEntity.builder() + .bookingItem(givenBookingItem) + .caption("some new managed webspace") + .type(HsHostingAssetType.MANAGED_WEBSPACE) + .identifier("xyz90") + .build(); + return toCleanup(assetRepo.save(newAsset)); + }); + + // then + result.assertSuccessful(); + assertThat(result.returnedValue()).isNotNull().extracting(HsHostingAssetEntity::getUuid).isNotNull(); + assertThatAssetIsPersisted(result.returnedValue()); + assertThat(assetRepo.count()).isEqualTo(count + 1); + } + + @Test + public void createsAndGrantsRoles() { + // given + context("superuser-alex@hostsharing.net"); + final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll()); + final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream() + .map(s -> s.replace("hs_office_", "")) + .toList(); + final var givenBookingItem = givenBookingItem("First", "some CloudServer"); + + // when + final var result = attempt(em, () -> { + final var newAsset = HsHostingAssetEntity.builder() + .bookingItem(givenBookingItem) + .type(HsHostingAssetType.MANAGED_WEBSPACE) + .identifier("xyz91") + .caption("some new managed webspace") + .build(); + return toCleanup(assetRepo.save(newAsset)); + }); + + // then + result.assertSuccessful(); + final var all = rawRoleRepo.findAll(); + assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from( + initialRoleNames, + "hs_hosting_asset#D-1000111-someCloudServer-xyz91:ADMIN", + "hs_hosting_asset#D-1000111-someCloudServer-xyz91:OWNER", + "hs_hosting_asset#D-1000111-someCloudServer-xyz91:TENANT")); + assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())) + .map(s -> s.replace("hs_office_", "")) + .containsExactlyInAnyOrder(fromFormatted( + initialGrantNames, + // global-admin + + // owner + "{ grant perm:hs_hosting_asset#D-1000111-someCloudServer-xyz91:DELETE to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:OWNER by system and assume }", + + // admin + "{ grant perm:hs_hosting_asset#D-1000111-someCloudServer-xyz91:UPDATE to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:ADMIN by system and assume }", + "{ grant role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:ADMIN to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:OWNER by system and assume }", + "{ grant role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:OWNER to role:hs_booking_item#D-1000111-someCloudServer:ADMIN by system and assume }", + + // tenant + "{ grant perm:hs_hosting_asset#D-1000111-someCloudServer-xyz91:SELECT to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:TENANT by system and assume }", + "{ grant role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:TENANT to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:ADMIN by system and assume }", + "{ grant role:hs_booking_item#D-1000111-someCloudServer:TENANT to role:hs_hosting_asset#D-1000111-someCloudServer-xyz91:TENANT by system and assume }", + + null)); + } + + private void assertThatAssetIsPersisted(final HsHostingAssetEntity saved) { + final var found = assetRepo.findByUuid(saved.getUuid()); + assertThat(found).isNotEmpty().map(HsHostingAssetEntity::toString).get().isEqualTo(saved.toString()); + } + } + + @Nested + class FindByDebitorUuid { + + @Test + public void globalAdmin_withoutAssumedRole_canViewAllAssetsOfArbitraryDebitor() { + // given + context("superuser-alex@hostsharing.net"); + final var debitorUuid = debitorRepo.findDebitorByDebitorNumber(1000212).stream() + .findAny().orElseThrow().getUuid(); + + // when + final var result = assetRepo.findAllByDebitorUuid(debitorUuid); + + // then + allTheseServersAreReturned( + result, + "HsHostingAssetEntity(D-1000212:some PrivateCloud, MANAGED_WEBSPACE, 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 })"); + } + + @Test + public void normalUser_canViewOnlyRelatedAsset() { + // given: + context("person-FirbySusan@example.com"); + final var debitorUuid = debitorRepo.findDebitorByDebitorNumber(1000111).stream().findAny().orElseThrow().getUuid(); + + // when: + final var result = assetRepo.findAllByDebitorUuid(debitorUuid); + + // then: + exactlyTheseAssetsAreReturned( + result, + "HsHostingAssetEntity(D-1000111:some PrivateCloud, MANAGED_WEBSPACE, 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 })"); + } + } + + @Nested + class UpdateAsset { + + @Test + public void hostsharingAdmin_canUpdateArbitraryServer() { + // given + final var givenAssetUuid = givenSomeTemporaryAsset("First", "vm1000").getUuid(); + + // when + final var result = jpaAttempt.transacted(() -> { + context("superuser-alex@hostsharing.net"); + final var foundAsset = em.find(HsHostingAssetEntity.class, givenAssetUuid); + foundAsset.getConfig().put("CPUs", 2); + foundAsset.getConfig().remove("SSD-storage"); + foundAsset.getConfig().put("HSD-storage", 2048); + return toCleanup(assetRepo.save(foundAsset)); + }); + + // then + result.assertSuccessful(); + jpaAttempt.transacted(() -> { + context("superuser-alex@hostsharing.net"); + assertThatAssetActuallyInDatabase(result.returnedValue()); + }).assertSuccessful(); + } + + private void assertThatAssetActuallyInDatabase(final HsHostingAssetEntity saved) { + final var found = assetRepo.findByUuid(saved.getUuid()); + assertThat(found).isNotEmpty().get().isNotSameAs(saved) + .extracting(Object::toString).isEqualTo(saved.toString()); + } + } + + @Nested + class DeleteByUuid { + + @Test + public void globalAdmin_withoutAssumedRole_canDeleteAnyAsset() { + // given + context("superuser-alex@hostsharing.net", null); + final var givenAsset = givenSomeTemporaryAsset("First", "vm1000"); + + // when + final var result = jpaAttempt.transacted(() -> { + context("superuser-alex@hostsharing.net"); + assetRepo.deleteByUuid(givenAsset.getUuid()); + }); + + // then + result.assertSuccessful(); + assertThat(jpaAttempt.transacted(() -> { + context("superuser-fran@hostsharing.net", null); + return assetRepo.findByUuid(givenAsset.getUuid()); + }).assertSuccessful().returnedValue()).isEmpty(); + } + + @Test + public void relatedOwner_canDeleteTheirRelatedAsset() { + // given + context("superuser-alex@hostsharing.net", null); + final var givenAsset = givenSomeTemporaryAsset("First", "vm1000"); + + // when + final var result = jpaAttempt.transacted(() -> { + context("person-FirbySusan@example.com"); + assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent(); + + assetRepo.deleteByUuid(givenAsset.getUuid()); + }); + + // then + result.assertSuccessful(); + assertThat(jpaAttempt.transacted(() -> { + context("superuser-fran@hostsharing.net", null); + return assetRepo.findByUuid(givenAsset.getUuid()); + }).assertSuccessful().returnedValue()).isEmpty(); + } + + @Test + public void relatedAdmin_canNotDeleteTheirRelatedAsset() { + // given + context("superuser-alex@hostsharing.net", null); + final var givenAsset = givenSomeTemporaryAsset("First", "vm1000"); + + // when + final var result = jpaAttempt.transacted(() -> { + context("person-FirbySusan@example.com", "hs_hosting_asset#D-1000111-someCloudServer-vm1000:ADMIN"); + assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent(); + + assetRepo.deleteByUuid(givenAsset.getUuid()); + }); + + // then + result.assertExceptionWithRootCauseMessage( + JpaSystemException.class, + "[403] Subject ", " is not allowed to delete hs_hosting_asset"); + assertThat(jpaAttempt.transacted(() -> { + context("superuser-alex@hostsharing.net"); + return assetRepo.findByUuid(givenAsset.getUuid()); + }).assertSuccessful().returnedValue()).isPresent(); // still there + } + + @Test + public void deletingAnAssetAlsoDeletesRelatedRolesAndGrants() { + // given + context("superuser-alex@hostsharing.net"); + final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll())); + final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll())); + final var givenAsset = givenSomeTemporaryAsset("First", "vm1000"); + + // when + final var result = jpaAttempt.transacted(() -> { + context("superuser-alex@hostsharing.net"); + return assetRepo.deleteByUuid(givenAsset.getUuid()); + }); + + // then + result.assertSuccessful(); + assertThat(result.returnedValue()).isEqualTo(1); + assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(initialRoleNames); + assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(initialGrantNames); + } + } + + @Test + public void auditJournalLogIsAvailable() { + // given + final var query = em.createNativeQuery(""" + select currentTask, targetTable, targetOp + from tx_journal_v + where targettable = 'hs_hosting_asset'; + """); + + // when + @SuppressWarnings("unchecked") final List customerLogEntries = query.getResultList(); + + // then + assertThat(customerLogEntries).map(Arrays::toString).contains( + "[creating hosting-asset test-data 1000111, hs_hosting_asset, INSERT]", + "[creating hosting-asset test-data 1000212, hs_hosting_asset, INSERT]", + "[creating hosting-asset test-data 1000313, hs_hosting_asset, INSERT]"); + } + + private HsHostingAssetEntity givenSomeTemporaryAsset(final String debitorName, final String identifier) { + return jpaAttempt.transacted(() -> { + context("superuser-alex@hostsharing.net"); + final var givenBookingItem = givenBookingItem(debitorName, "some CloudServer"); + final var newAsset = HsHostingAssetEntity.builder() + .bookingItem(givenBookingItem) + .type(CLOUD_SERVER) + .identifier(identifier) + .caption("some temp cloud asset") + .config(Map.ofEntries( + entry("CPUs", 1), + entry("SSD-storage", 256))) + .build(); + + return toCleanup(assetRepo.save(newAsset)); + }).assertSuccessful().returnedValue(); + } + + HsBookingItemEntity givenBookingItem(final String debitorName, final String bookingItemCaption) { + final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).stream().findAny().orElseThrow(); + return bookingItemRepo.findAllByDebitorUuid(givenDebitor.getUuid()).stream() + .filter(i -> i.getCaption().equals(bookingItemCaption)) + .findAny().orElseThrow(); + } + + void exactlyTheseAssetsAreReturned( + final List actualResult, + final String... serverNames) { + assertThat(actualResult) + .extracting(HsHostingAssetEntity::toString) + .containsExactlyInAnyOrder(serverNames); + } + + void allTheseServersAreReturned(final List actualResult, final String... serverNames) { + assertThat(actualResult) + .extracting(HsHostingAssetEntity::toString) + .contains(serverNames); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java index 417771a3..3bc64cd1 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java @@ -620,6 +620,7 @@ public class ImportOfficeData extends ContextBasedTest { private void deleteTestDataFromHsOfficeTables() { jpaAttempt.transacted(() -> { context(rbacSuperuser); + em.createNativeQuery("delete from hs_hosting_asset where true").executeUpdate(); em.createNativeQuery("delete from hs_booking_item where true").executeUpdate(); em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate(); em.createNativeQuery("delete from hs_office_coopassetstransaction_legacy_id where true").executeUpdate();