From 9418303b7c8acfa3c29c33013b164c01aa8da10d Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 21 Jun 2024 12:02:07 +0200 Subject: [PATCH] add optional alarm-contact to hosting-asset (#64) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/64 Reviewed-by: Marc Sandlus --- .../asset/HsHostingAssetController.java | 7 +- .../hosting/asset/HsHostingAssetEntity.java | 17 +- .../asset/HsHostingAssetEntityPatcher.java | 12 +- .../hs-hosting/hs-hosting-asset-schemas.yaml | 10 + .../6303-hs-booking-item-rbac.md | 63 ++++ .../6303-hs-booking-item-rbac.sql | 277 ++++++++++++++++++ .../7010-hs-hosting-asset.sql | 1 + ...7013-hs-hosting-asset-rbac-CLOUD_SERVER.md | 72 ----- ...13-hs-hosting-asset-rbac-MANAGED_SERVER.md | 72 ----- ...-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md | 73 ----- .../7013-hs-hosting-asset-rbac.md | 58 +++- .../7013-hs-hosting-asset-rbac.sql | 103 +++---- .../hsadminng/arch/ArchitectureTest.java | 7 +- ...sBookingItemRepositoryIntegrationTest.java | 6 +- ...sHostingAssetControllerAcceptanceTest.java | 26 +- .../HsHostingAssetEntityPatcherUnitTest.java | 25 +- ...HostingAssetRepositoryIntegrationTest.java | 1 + 17 files changed, 544 insertions(+), 286 deletions(-) create mode 100644 src/main/resources/db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.md create mode 100644 src/main/resources/db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.sql delete mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-CLOUD_SERVER.md delete mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_SERVER.md delete mode 100644 src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java index d3578833..b7982328 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java @@ -16,7 +16,9 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; +import jakarta.persistence.EntityManager; import jakarta.persistence.EntityNotFoundException; +import jakarta.persistence.PersistenceContext; import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; @@ -26,6 +28,9 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAss @RestController public class HsHostingAssetController implements HsHostingAssetsApi { + @PersistenceContext + private EntityManager em; + @Autowired private Context context; @@ -119,7 +124,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi { final var current = assetRepo.findByUuid(assetUuid).orElseThrow(); - new HsHostingAssetEntityPatcher(current).apply(body); + new HsHostingAssetEntityPatcher(em, current).apply(body); final var saved = validated(assetRepo.save(current)); final var mapped = mapper.map(saved, HsHostingAssetResource.class); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index fa15537a..76bcd40d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java @@ -8,6 +8,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; @@ -48,6 +49,7 @@ 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.REFERRER; 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; @@ -95,6 +97,10 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @Enumerated(EnumType.STRING) private HsHostingAssetType type; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "alarmcontactuuid") + private HsOfficeContactEntity alarmContact; + @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name="parentassetuuid", referencedColumnName="uuid") private List subHostingAssets; @@ -136,7 +142,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { return rbacViewFor("asset", HsHostingAssetEntity.class) .withIdentityView(SQL.projection("identifier")) .withRestrictedViewOrderBy(SQL.expression("identifier")) - .withUpdatableColumns("version", "caption", "config") + .withUpdatableColumns("version", "caption", "config", "assignedToAssetUuid", "alarmContactUuid") .toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data? .importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(), @@ -155,6 +161,11 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { directlyFetchedByDependsOnColumn(), NULLABLE) + .importEntityAlias("alarmContact", HsOfficeContactEntity.class, usingDefaultCase(), + dependsOnColumn("alarmContactUuid"), + directlyFetchedByDependsOnColumn(), + NULLABLE) + .createRole(OWNER, (with) -> { with.incomingSuperRole("bookingItem", ADMIN); with.incomingSuperRole("parentAsset", ADMIN); @@ -167,13 +178,15 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { }) .createSubRole(AGENT, (with) -> { with.outgoingSubRole("assignedToAsset", TENANT); + with.outgoingSubRole("alarmContact", REFERRER); }) .createSubRole(TENANT, (with) -> { with.outgoingSubRole("bookingItem", TENANT); with.outgoingSubRole("parentAsset", TENANT); + with.incomingSuperRole("alarmContact", ADMIN); with.permission(SELECT); }) - .limitDiagramTo("asset", "bookingItem", "bookingItem.debitorRel", "parentAsset", "assignedToAsset", "global"); + .limitDiagramTo("asset", "bookingItem", "bookingItem.debitorRel", "parentAsset", "assignedToAsset", "alarmContact", "global"); } public static void main(String[] args) throws IOException { 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 index a555be19..f1cff713 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcher.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcher.java @@ -1,17 +1,21 @@ package net.hostsharing.hsadminng.hs.hosting.asset; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource; +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.KeyValueMap; import net.hostsharing.hsadminng.mapper.OptionalFromJson; +import jakarta.persistence.EntityManager; import java.util.Optional; public class HsHostingAssetEntityPatcher implements EntityPatcher { + private final EntityManager em; private final HsHostingAssetEntity entity; - public HsHostingAssetEntityPatcher(final HsHostingAssetEntity entity) { + HsHostingAssetEntityPatcher(final EntityManager em, final HsHostingAssetEntity entity) { + this.em = em; this.entity = entity; } @@ -21,5 +25,11 @@ public class HsHostingAssetEntityPatcher implements EntityPatcher entity.getConfig().patch(KeyValueMap.from(resource.getConfig()))); + OptionalFromJson.of(resource.getAlarmContactUuid()) + // HOWTO: patch nullable JSON resource uuid to an ntity reference + .ifPresent(newValue -> entity.setAlarmContact( + Optional.ofNullable(newValue) + .map(uuid -> em.getReference(HsOfficeContactEntity.class, newValue)) + .orElse(null))); } } diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml index 8e9dbe02..934c9647 100644 --- a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml @@ -32,6 +32,8 @@ components: type: string caption: type: string + alarmContact: + $ref: '../hs-office/hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact' config: $ref: '#/components/schemas/HsHostingAssetConfiguration' required: @@ -46,6 +48,10 @@ components: caption: type: string nullable: true + alarmContactUuid: + type: string + format: uuid + nullable: true config: $ref: '#/components/schemas/HsHostingAssetConfiguration' @@ -72,6 +78,10 @@ components: minLength: 3 maxLength: 80 nullable: false + alarmContactUuid: + type: string + format: uuid + nullable: true config: $ref: '#/components/schemas/HsHostingAssetConfiguration' required: diff --git a/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.md b/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.md new file mode 100644 index 00000000..4775616f --- /dev/null +++ b/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.md @@ -0,0 +1,63 @@ +### rbac bookingItem + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph bookingItem["`**bookingItem**`"] + direction TB + style bookingItem fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph bookingItem:roles[ ] + style bookingItem:roles fill:#dd4901,stroke:white + + role:bookingItem:OWNER[[bookingItem:OWNER]] + role:bookingItem:ADMIN[[bookingItem:ADMIN]] + role:bookingItem:AGENT[[bookingItem:AGENT]] + role:bookingItem:TENANT[[bookingItem:TENANT]] + end + + subgraph bookingItem:permissions[ ] + style bookingItem:permissions fill:#dd4901,stroke:white + + perm:bookingItem:INSERT{{bookingItem:INSERT}} + perm:bookingItem:DELETE{{bookingItem:DELETE}} + perm:bookingItem:UPDATE{{bookingItem:UPDATE}} + perm:bookingItem:SELECT{{bookingItem:SELECT}} + end +end + +subgraph project["`**project**`"] + direction TB + style project fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph project:roles[ ] + style project:roles fill:#99bcdb,stroke:white + + role:project:OWNER[[project:OWNER]] + role:project:ADMIN[[project:ADMIN]] + role:project:AGENT[[project:AGENT]] + role:project:TENANT[[project:TENANT]] + end +end + +%% granting roles to roles +role:project:OWNER -.-> role:project:ADMIN +role:project:ADMIN -.-> role:project:AGENT +role:project:AGENT -.-> role:project:TENANT +role:project:AGENT ==> role:bookingItem:OWNER +role:bookingItem:OWNER ==> role:bookingItem:ADMIN +role:bookingItem:ADMIN ==> role:bookingItem:AGENT +role:bookingItem:AGENT ==> role:bookingItem:TENANT +role:bookingItem:TENANT ==> role:project:TENANT + +%% granting permissions to roles +role:global:ADMIN ==> perm:bookingItem:INSERT +role:global:ADMIN ==> perm:bookingItem:DELETE +role:project:ADMIN ==> perm:bookingItem:INSERT +role:bookingItem:ADMIN ==> perm:bookingItem:UPDATE +role:bookingItem:TENANT ==> perm:bookingItem:SELECT + +``` diff --git a/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.sql b/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.sql new file mode 100644 index 00000000..bcd6523e --- /dev/null +++ b/src/main/resources/db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.sql @@ -0,0 +1,277 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-booking-item-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_booking_item'); +--// + + +-- ============================================================================ +--changeset hs-booking-item-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsBookingItem', 'hs_booking_item'); +--// + + +-- ============================================================================ +--changeset hs-booking-item-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsBookingItem( + NEW hs_booking_item +) + language plpgsql as $$ + +declare + newProject hs_booking_project; + newParentItem hs_booking_item; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT * FROM hs_booking_project WHERE uuid = NEW.projectUuid INTO newProject; + + SELECT * FROM hs_booking_item WHERE uuid = NEW.parentItemUuid INTO newParentItem; + + perform createRoleWithGrants( + hsBookingItemOWNER(NEW), + incomingSuperRoles => array[ + hsBookingItemAGENT(newParentItem), + hsBookingProjectAGENT(newProject)] + ); + + perform createRoleWithGrants( + hsBookingItemADMIN(NEW), + permissions => array['UPDATE'], + incomingSuperRoles => array[hsBookingItemOWNER(NEW)] + ); + + perform createRoleWithGrants( + hsBookingItemAGENT(NEW), + incomingSuperRoles => array[hsBookingItemADMIN(NEW)] + ); + + perform createRoleWithGrants( + hsBookingItemTENANT(NEW), + permissions => array['SELECT'], + incomingSuperRoles => array[hsBookingItemAGENT(NEW)], + outgoingSubRoles => array[ + hsBookingItemTENANT(newParentItem), + hsBookingProjectTENANT(newProject)] + ); + + + + call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), globalAdmin()); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_booking_item row. + */ + +create or replace function insertTriggerForHsBookingItem_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsBookingItem(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsBookingItem_tg + after insert on hs_booking_item + for each row +execute procedure insertTriggerForHsBookingItem_tf(); +--// + + +-- ============================================================================ +--changeset hs-booking-item-rbac-GRANTING-INSERT-PERMISSION:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +-- granting INSERT permission to global ---------------------------- + +/* + Grants INSERT INTO hs_booking_item permissions to specified role of pre-existing global rows. + */ +do language plpgsql $$ + declare + row global; + begin + call defineContext('create INSERT INTO hs_booking_item permissions for pre-exising global rows'); + + FOR row IN SELECT * FROM global + -- unconditional for all rows in that table + LOOP + call grantPermissionToRole( + createPermission(row.uuid, 'INSERT', 'hs_booking_item'), + globalADMIN()); + END LOOP; + end; +$$; + +/** + Grants hs_booking_item INSERT permission to specified role of new global rows. +*/ +create or replace function new_hs_booking_item_grants_insert_to_global_tf() + returns trigger + language plpgsql + strict as $$ +begin + -- unconditional for all rows in that table + call grantPermissionToRole( + createPermission(NEW.uuid, 'INSERT', 'hs_booking_item'), + globalADMIN()); + -- end. + return NEW; +end; $$; + +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_new_hs_booking_item_grants_insert_to_global_tg + after insert on global + for each row +execute procedure new_hs_booking_item_grants_insert_to_global_tf(); + +-- granting INSERT permission to hs_booking_project ---------------------------- + +/* + Grants INSERT INTO hs_booking_item permissions to specified role of pre-existing hs_booking_project rows. + */ +do language plpgsql $$ + declare + row hs_booking_project; + begin + call defineContext('create INSERT INTO hs_booking_item permissions for pre-exising hs_booking_project rows'); + + FOR row IN SELECT * FROM hs_booking_project + -- unconditional for all rows in that table + LOOP + call grantPermissionToRole( + createPermission(row.uuid, 'INSERT', 'hs_booking_item'), + hsBookingProjectADMIN(row)); + END LOOP; + end; +$$; + +/** + Grants hs_booking_item INSERT permission to specified role of new hs_booking_project rows. +*/ +create or replace function new_hs_booking_item_grants_insert_to_hs_booking_project_tf() + returns trigger + language plpgsql + strict as $$ +begin + -- unconditional for all rows in that table + call grantPermissionToRole( + createPermission(NEW.uuid, 'INSERT', 'hs_booking_item'), + hsBookingProjectADMIN(NEW)); + -- end. + return NEW; +end; $$; + +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_new_hs_booking_item_grants_insert_to_hs_booking_project_tg + after insert on hs_booking_project + for each row +execute procedure new_hs_booking_item_grants_insert_to_hs_booking_project_tf(); + +-- granting INSERT permission to hs_booking_item ---------------------------- + +-- Granting INSERT INTO hs_hosting_asset permissions to specified role of pre-existing hs_hosting_asset rows slipped, +-- because there cannot yet be any pre-existing rows in the same table yet. + +/** + Grants hs_booking_item INSERT permission to specified role of new hs_booking_item rows. +*/ +create or replace function new_hs_booking_item_grants_insert_to_hs_booking_item_tf() + returns trigger + language plpgsql + strict as $$ +begin + -- unconditional for all rows in that table + call grantPermissionToRole( + createPermission(NEW.uuid, 'INSERT', 'hs_booking_item'), + hsBookingItemADMIN(NEW)); + -- end. + return NEW; +end; $$; + +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_new_hs_booking_item_grants_insert_to_hs_booking_item_tg + after insert on hs_booking_item + for each row +execute procedure new_hs_booking_item_grants_insert_to_hs_booking_item_tf(); + + +-- ============================================================================ +--changeset hs_booking_item-rbac-CHECKING-INSERT-PERMISSION:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/** + Checks if the user respectively the assumed roles are allowed to insert a row to hs_booking_item. +*/ +create or replace function hs_booking_item_insert_permission_check_tf() + returns trigger + language plpgsql as $$ +declare + superObjectUuid uuid; +begin + -- check INSERT INSERT if global ADMIN + if isGlobalAdmin() then + return NEW; + end if; + -- check INSERT permission via direct foreign key: NEW.projectUuid + if hasInsertPermission(NEW.projectUuid, 'hs_booking_item') then + return NEW; + end if; + -- check INSERT permission via direct foreign key: NEW.parentItemUuid + if hasInsertPermission(NEW.parentItemUuid, 'hs_booking_item') then + return NEW; + end if; + + raise exception '[403] insert into hs_booking_item values(%) not allowed for current subjects % (%)', + NEW, currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_booking_item_insert_permission_check_tg + before insert on hs_booking_item + for each row + execute procedure hs_booking_item_insert_permission_check_tf(); +--// + + +-- ============================================================================ +--changeset hs-booking-item-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +call generateRbacIdentityViewFromProjection('hs_booking_item', + $idName$ + caption + $idName$); +--// + + +-- ============================================================================ +--changeset hs-booking-item-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_booking_item', + $orderBy$ + validity + $orderBy$, + $updates$ + version = new.version, + caption = new.caption, + validity = new.validity, + resources = new.resources + $updates$); +--// + diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql index 7e96a3fd..bd6ff6e4 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql @@ -33,6 +33,7 @@ create table if not exists hs_hosting_asset identifier varchar(80) not null, caption varchar(80), config jsonb not null, + alarmContactUuid uuid null references hs_office_contact(uuid) initially deferred, constraint chk_hs_hosting_asset_has_booking_item_or_parent_asset check (bookingItemUuid is not null or parentAssetUuid is not null) 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 deleted file mode 100644 index c4abe818..00000000 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-CLOUD_SERVER.md +++ /dev/null @@ -1,72 +0,0 @@ -### rbac asset inCaseOf:CLOUD_SERVER - -This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. - -```mermaid -%%{init:{'flowchart':{'htmlLabels':false}}}%% -flowchart TB - -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 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**`"] - direction TB - style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px - - subgraph parentServer:roles[ ] - style parentServer:roles fill:#99bcdb,stroke:white - - role:parentServer:ADMIN[[parentServer:ADMIN]] - end -end - -%% granting roles to roles -role:bookingItem:OWNER -.-> role:bookingItem:ADMIN -role:bookingItem:ADMIN -.-> role:bookingItem:AGENT -role:bookingItem:AGENT -.-> role:bookingItem: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 -role:global:ADMIN ==> perm:asset:INSERT - -``` diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_SERVER.md b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_SERVER.md deleted file mode 100644 index 5d9b4710..00000000 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_SERVER.md +++ /dev/null @@ -1,72 +0,0 @@ -### rbac asset inCaseOf:MANAGED_SERVER - -This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. - -```mermaid -%%{init:{'flowchart':{'htmlLabels':false}}}%% -flowchart TB - -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 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**`"] - direction TB - style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px - - subgraph parentServer:roles[ ] - style parentServer:roles fill:#99bcdb,stroke:white - - role:parentServer:ADMIN[[parentServer:ADMIN]] - end -end - -%% granting roles to roles -role:bookingItem:OWNER -.-> role:bookingItem:ADMIN -role:bookingItem:ADMIN -.-> role:bookingItem:AGENT -role:bookingItem:AGENT -.-> role:bookingItem: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 -role:global:ADMIN ==> perm:asset:INSERT - -``` diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md deleted file mode 100644 index 5a35b108..00000000 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac-MANAGED_WEBSPACE.md +++ /dev/null @@ -1,73 +0,0 @@ -### rbac asset inCaseOf:MANAGED_WEBSPACE - -This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. - -```mermaid -%%{init:{'flowchart':{'htmlLabels':false}}}%% -flowchart TB - -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 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**`"] - direction TB - style parentServer fill:#99bcdb,stroke:#274d6e,stroke-width:8px - - subgraph parentServer:roles[ ] - style parentServer:roles fill:#99bcdb,stroke:white - - role:parentServer:ADMIN[[parentServer:ADMIN]] - end -end - -%% granting roles to roles -role:bookingItem:OWNER -.-> role:bookingItem:ADMIN -role:bookingItem:ADMIN -.-> role:bookingItem:AGENT -role:bookingItem:AGENT -.-> role:bookingItem: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:parentServer:ADMIN ==> perm:asset:INSERT -role:asset:OWNER ==> perm:asset:DELETE -role:asset:ADMIN ==> perm:asset:UPDATE -role:asset:TENANT ==> perm:asset:SELECT -role:global:ADMIN ==> perm:asset:INSERT - -``` diff --git a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md index bf7780e1..f0b250db 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.md @@ -6,6 +6,19 @@ This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manua %%{init:{'flowchart':{'htmlLabels':false}}}%% flowchart TB +subgraph alarmContact["`**alarmContact**`"] + direction TB + style alarmContact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph alarmContact:roles[ ] + style alarmContact:roles fill:#99bcdb,stroke:white + + role:alarmContact:OWNER[[alarmContact:OWNER]] + role:alarmContact:ADMIN[[alarmContact:ADMIN]] + role:alarmContact:REFERRER[[alarmContact:REFERRER]] + end +end + subgraph asset["`**asset**`"] direction TB style asset fill:#dd4901,stroke:#274d6e,stroke-width:8px @@ -25,6 +38,7 @@ subgraph asset["`**asset**`"] perm:asset:INSERT{{asset:INSERT}} perm:asset:DELETE{{asset:DELETE}} perm:asset:UPDATE{{asset:UPDATE}} + perm:asset:SELECT{{asset:SELECT}} end end @@ -39,16 +53,58 @@ subgraph assignedToAsset["`**assignedToAsset**`"] 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 parentAsset["`**parentAsset**`"] + direction TB + style parentAsset fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph parentAsset:roles[ ] + style parentAsset:roles fill:#99bcdb,stroke:white + + role:parentAsset:ADMIN[[parentAsset:ADMIN]] + role:parentAsset:AGENT[[parentAsset:AGENT]] + role:parentAsset:TENANT[[parentAsset:TENANT]] + end +end + %% granting roles to roles +role:bookingItem:OWNER -.-> role:bookingItem:ADMIN +role:bookingItem:ADMIN -.-> role:bookingItem:AGENT +role:bookingItem:AGENT -.-> role:bookingItem:TENANT +role:global:ADMIN -.-> role:alarmContact:OWNER +role:alarmContact:OWNER -.-> role:alarmContact:ADMIN +role:alarmContact:ADMIN -.-> role:alarmContact:REFERRER +role:bookingItem:ADMIN ==> role:asset:OWNER +role:parentAsset:ADMIN ==> role:asset:OWNER role:asset:OWNER ==> role:asset:ADMIN +role:bookingItem:AGENT ==> role:asset:ADMIN +role:parentAsset:AGENT ==> role:asset:ADMIN role:asset:ADMIN ==> role:asset:AGENT role:asset:AGENT ==> role:assignedToAsset:TENANT +role:asset:AGENT ==> role:alarmContact:REFERRER role:asset:AGENT ==> role:asset:TENANT -role:assignedToAsset:TENANT ==> role:asset:TENANT +role:asset:TENANT ==> role:bookingItem:TENANT +role:asset:TENANT ==> role:parentAsset:TENANT +role:alarmContact:ADMIN ==> role:asset:TENANT %% granting permissions to roles role:global:ADMIN ==> perm:asset:INSERT +role:parentAsset:ADMIN ==> 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 index f14430a7..cbaffa47 100644 --- a/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql +++ b/src/main/resources/db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql @@ -32,6 +32,7 @@ create or replace procedure buildRbacSystemForHsHostingAsset( declare newBookingItem hs_booking_item; newAssignedToAsset hs_hosting_asset; + newAlarmContact hs_office_contact; newParentAsset hs_hosting_asset; begin @@ -41,6 +42,8 @@ begin SELECT * FROM hs_hosting_asset WHERE uuid = NEW.assignedToAssetUuid INTO newAssignedToAsset; + SELECT * FROM hs_office_contact WHERE uuid = NEW.alarmContactUuid INTO newAlarmContact; + SELECT * FROM hs_hosting_asset WHERE uuid = NEW.parentAssetUuid INTO newParentAsset; perform createRoleWithGrants( @@ -63,14 +66,17 @@ begin perform createRoleWithGrants( hsHostingAssetAGENT(NEW), incomingSuperRoles => array[hsHostingAssetADMIN(NEW)], - outgoingSubRoles => array[hsHostingAssetTENANT(newAssignedToAsset)] + outgoingSubRoles => array[ + hsHostingAssetTENANT(newAssignedToAsset), + hsOfficeContactREFERRER(newAlarmContact)] ); perform createRoleWithGrants( hsHostingAssetTENANT(NEW), + permissions => array['SELECT'], incomingSuperRoles => array[ hsHostingAssetAGENT(NEW), - hsHostingAssetTENANT(newAssignedToAsset)], + hsOfficeContactADMIN(newAlarmContact)], outgoingSubRoles => array[ hsBookingItemTENANT(newBookingItem), hsHostingAssetTENANT(newParentAsset)] @@ -99,6 +105,48 @@ execute procedure insertTriggerForHsHostingAsset_tf(); --// +-- ============================================================================ +--changeset hs-hosting-asset-rbac-update-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Called from the AFTER UPDATE TRIGGER to re-wire the grants. + */ + +create or replace procedure updateRbacRulesForHsHostingAsset( + OLD hs_hosting_asset, + NEW hs_hosting_asset +) + language plpgsql as $$ +begin + + if NEW.assignedToAssetUuid is distinct from OLD.assignedToAssetUuid + or NEW.alarmContactUuid is distinct from OLD.alarmContactUuid then + delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; + call buildRbacSystemForHsHostingAsset(NEW); + end if; +end; $$; + +/* + AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_hosting_asset row. + */ + +create or replace function updateTriggerForHsHostingAsset_tf() + returns trigger + language plpgsql + strict as $$ +begin + call updateRbacRulesForHsHostingAsset(OLD, NEW); + return NEW; +end; $$; + +create trigger updateTriggerForHsHostingAsset_tg + after update on hs_hosting_asset + for each row +execute procedure updateTriggerForHsHostingAsset_tf(); +--// + + -- ============================================================================ --changeset hs-hosting-asset-rbac-GRANTING-INSERT-PERMISSION:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -146,49 +194,6 @@ create trigger z_new_hs_hosting_asset_grants_insert_to_global_tg for each row execute procedure new_hs_hosting_asset_grants_insert_to_global_tf(); --- granting INSERT permission to hs_booking_item ---------------------------- - -/* - Grants INSERT INTO hs_hosting_asset permissions to specified role of pre-existing hs_booking_item rows. - */ -do language plpgsql $$ - declare - row hs_booking_item; - begin - call defineContext('create INSERT INTO hs_hosting_asset permissions for pre-exising hs_booking_item rows'); - - FOR row IN SELECT * FROM hs_booking_item - -- unconditional for all rows in that table - LOOP - call grantPermissionToRole( - createPermission(row.uuid, 'INSERT', 'hs_hosting_asset'), - hsBookingItemAGENT(row)); - END LOOP; - end; -$$; - -/** - Grants hs_hosting_asset INSERT permission to specified role of new hs_booking_item rows. -*/ -create or replace function new_hs_hosting_asset_grants_insert_to_hs_booking_item_tf() - returns trigger - language plpgsql - strict as $$ -begin - -- unconditional for all rows in that table - call grantPermissionToRole( - createPermission(NEW.uuid, 'INSERT', 'hs_hosting_asset'), - hsBookingItemAGENT(NEW)); - -- end. - return NEW; -end; $$; - --- z_... is to put it at the end of after insert triggers, to make sure the roles exist -create trigger z_new_hs_hosting_asset_grants_insert_to_hs_booking_item_tg - after insert on hs_booking_item - for each row -execute procedure new_hs_hosting_asset_grants_insert_to_hs_booking_item_tf(); - -- granting INSERT permission to hs_hosting_asset ---------------------------- -- Granting INSERT INTO hs_hosting_asset permissions to specified role of pre-existing hs_hosting_asset rows slipped, @@ -234,10 +239,6 @@ begin if isGlobalAdmin() then return NEW; end if; - -- check INSERT permission via direct foreign key: NEW.bookingItemUuid - if hasInsertPermission(NEW.bookingItemUuid, 'hs_hosting_asset') then - return NEW; - end if; -- check INSERT permission via direct foreign key: NEW.parentAssetUuid if hasInsertPermission(NEW.parentAssetUuid, 'hs_hosting_asset') then return NEW; @@ -275,7 +276,9 @@ call generateRbacRestrictedView('hs_hosting_asset', $updates$ version = new.version, caption = new.caption, - config = new.config + config = new.config, + assignedToAssetUuid = new.assignedToAssetUuid, + alarmContactUuid = new.alarmContactUuid $updates$); --// diff --git a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java index df26279d..f626a3ed 100644 --- a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java +++ b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java @@ -150,7 +150,8 @@ public class ArchitectureTest { .should().onlyBeAccessed().byClassesThat() .resideInAnyPackage( "..hs.booking.(*)..", - "..hs.hosting.(*).." + "..hs.hosting.(*)..", + "..hs.validation" // TODO.impl: Some Validators need to be refactored to booking package. ); @ArchTest @@ -195,7 +196,9 @@ public class ArchitectureTest { "..hs.office.partner..", "..hs.office.debitor..", "..hs.office.membership..", - "..hs.office.migration.."); + "..hs.office.migration..", + "..hs.hosting.asset.." + ); @ArchTest @SuppressWarnings("unused") 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 b474d0c7..f125974a 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 @@ -97,9 +97,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup // 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 initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()); // when attempt(em, () -> { @@ -124,7 +122,6 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup "hs_booking_item#somenewbookingitem:OWNER", "hs_booking_item#somenewbookingitem:TENANT")); assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())) - .map(s -> s.replace("hs_office_", "")) .containsExactlyInAnyOrder(fromFormatted( initialGrantNames, @@ -138,7 +135,6 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup // admin "{ grant perm:hs_booking_item#somenewbookingitem:UPDATE to role:hs_booking_item#somenewbookingitem:ADMIN by system and assume }", "{ grant role:hs_booking_item#somenewbookingitem:ADMIN to role:hs_booking_item#somenewbookingitem:OWNER by system and assume }", - "{ grant perm:hs_booking_item#somenewbookingitem:INSERT>hs_hosting_asset to role:hs_booking_item#somenewbookingitem:AGENT by system and assume }", // agent "{ grant role:hs_booking_item#somenewbookingitem:AGENT to role:hs_booking_item#somenewbookingitem:ADMIN by system and assume }", diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index 84fe1627..44f5327f 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -7,6 +7,8 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository; +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; @@ -54,6 +56,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup @Autowired HsOfficeDebitorRepository debitorRepo; + @Autowired + HsOfficeContactRepository contactRepo; + @Autowired JpaAttempt jpaAttempt; @@ -425,6 +430,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup final var givenAsset = givenSomeTemporaryHostingAsset("2001", MANAGED_SERVER, config("monit_max_ssd_usage", 80), config("monit_max_hdd_usage", 90), config("monit_max_cpu_usage", 90), config("monit_max_ram_usage", 70)); + final var alarmContactUuid = givenContact().getUuid(); RestAssured // @formatter:off .given() @@ -432,13 +438,14 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup .contentType(ContentType.JSON) .body(""" { + "alarmContactUuid": "%s", "config": { "monit_max_ssd_usage": 85, "monit_max_hdd_usage": null, "monit_min_free_ssd": 5 } } - """) + """.formatted(alarmContactUuid)) .port(port) .when() .patch("http://localhost/api/hs/hosting/assets/" + givenAsset.getUuid()) @@ -450,6 +457,11 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "type": "MANAGED_SERVER", "identifier": "vm2001", "caption": "some test-asset", + "alarmContact": { + "uuid": "%s", + "caption": "second contact", + "emailAddresses": { "main": "contact-admin@secondcontact.example.com" } + }, "config": { "monit_max_cpu_usage": 90, "monit_max_ram_usage": 70, @@ -457,12 +469,15 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "monit_min_free_ssd": 5 } } - """)); // @formatter:on + """.formatted(alarmContactUuid))); + // @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.getAlarmContact().toString()).isEqualTo( + "contact(caption='second contact', emailAddresses='{ main: contact-admin@secondcontact.example.com }')"); assertThat(asset.getConfig().toString()).isEqualTo( "{ monit_max_cpu_usage: 90, monit_max_ram_usage: 70, monit_max_ssd_usage: 85, monit_min_free_ssd: 5 }"); return true; @@ -470,6 +485,13 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup } } + private HsOfficeContactEntity givenContact() { + return jpaAttempt.transacted(() -> { + context.define("superuser-alex@hostsharing.net"); + return contactRepo.findContactByOptionalCaptionLike("second").stream().findFirst().orElseThrow(); + }).returnedValue(); + } + @Nested @Order(5) class DeleteAsset { 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 index 2530f5fa..890932b4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java @@ -1,7 +1,7 @@ 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.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.mapper.KeyValueMap; import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; import org.junit.jupiter.api.BeforeEach; @@ -31,6 +31,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< > { private static final UUID INITIAL_BOOKING_ITEM_UUID = UUID.randomUUID(); + private static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID(); private static final Map INITIAL_CONFIG = patchMap( entry("CPU", 1), @@ -47,6 +48,9 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< entry("SSD", 256), entry("MEM", 64) ); + final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder() + .uuid(UUID.randomUUID()) + .build(); private static final String INITIAL_CAPTION = "initial caption"; private static final String PATCHED_CAPTION = "patched caption"; @@ -56,10 +60,10 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< @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()); + lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> + HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); } @Override @@ -69,6 +73,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< entity.setBookingItem(TEST_BOOKING_ITEM); entity.getConfig().putAll(KeyValueMap.from(INITIAL_CONFIG)); entity.setCaption(INITIAL_CAPTION); + entity.setAlarmContact(givenInitialContact); return entity; } @@ -79,7 +84,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< @Override protected HsHostingAssetEntityPatcher createPatcher(final HsHostingAssetEntity server) { - return new HsHostingAssetEntityPatcher(server); + return new HsHostingAssetEntityPatcher(em, server); } @Override @@ -96,7 +101,17 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< PATCH_CONFIG, HsHostingAssetEntity::putConfig, PATCHED_CONFIG) - .notNullable() + .notNullable(), + new JsonNullableProperty<>( + "alarmContact", + HsHostingAssetPatchResource::setAlarmContactUuid, + PATCHED_CONTACT_UUID, + HsHostingAssetEntity::setAlarmContact, + newContact(PATCHED_CONTACT_UUID)) ); } + + static HsOfficeContactEntity newContact(final UUID uuid) { + return HsOfficeContactEntity.builder().uuid(uuid).build(); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java index f4abe06c..e5bc1605 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -147,6 +147,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu "{ grant role:hs_booking_item#fir01:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }", "{ grant role:hs_hosting_asset#fir00:TENANT to role:hs_hosting_asset#fir00:AGENT by system and assume }", "{ grant role:hs_hosting_asset#vm1011:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }", + "{ grant perm:hs_hosting_asset#fir00:SELECT to role:hs_hosting_asset#fir00:TENANT by system and assume }", null)); }