From d7a57fd112394d31458a11d5a3060fad5ac3a6a2 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 17 Jun 2024 06:26:30 +0200 Subject: [PATCH 1/5] add alarm contact to hosting asset --- .../hosting/asset/HsHostingAssetEntity.java | 18 +- .../contact/HsHostingContactEntity.java | 57 ++++ .../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 | 102 +++---- ...sBookingItemRepositoryIntegrationTest.java | 6 +- ...HostingAssetRepositoryIntegrationTest.java | 1 + .../HsHostingContactEntityUnitTest.java | 20 ++ 13 files changed, 545 insertions(+), 275 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntity.java 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 create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntityUnitTest.java 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..a99b4f2a 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,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import net.hostsharing.hsadminng.hs.hosting.contact.HsHostingContactEntity; +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 +50,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 +98,10 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @Enumerated(EnumType.STRING) private HsHostingAssetType type; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "alarmcontactuuid") + private HsHostingContactEntity alarmContactUuid; + @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name="parentassetuuid", referencedColumnName="uuid") private List subHostingAssets; @@ -136,7 +143,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 +162,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 +179,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/contact/HsHostingContactEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntity.java new file mode 100644 index 00000000..8d14865b --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntity.java @@ -0,0 +1,57 @@ +package net.hostsharing.hsadminng.hs.hosting.contact; + +import io.hypersistence.utils.hibernate.type.json.JsonType; +import lombok.*; +import lombok.experimental.FieldNameConstants; +import net.hostsharing.hsadminng.errors.DisplayName; +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.*; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static net.hostsharing.hsadminng.stringify.Stringify.stringify; + +@Entity +@Table(name = "hs_office_contact_rv") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +@DisplayName("Contact") +public class HsHostingContactEntity implements Stringifyable, RbacObject { + + private static Stringify toString = stringify(HsHostingContactEntity.class, "contact") + .withProp(HsHostingContactEntity.Fields.caption, HsHostingContactEntity::getCaption) + .withProp(HsHostingContactEntity.Fields.emailAddresses, HsHostingContactEntity::getEmailAddresses); + + @Id + private UUID uuid; + + @Version + private int version; + + @Column(name = "caption") + private String caption; + + @Builder.Default + @Setter(AccessLevel.NONE) + @Type(JsonType.class) + @Column(name = "emailaddresses") + private Map emailAddresses = new HashMap<>(); + + @Override + public String toString() { + return toString.apply(this); + } + + @Override + public String toShortString() { + return caption; + } +} 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..8a8b8c42 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,47 @@ 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 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 +193,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 +238,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 +275,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/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/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)); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntityUnitTest.java new file mode 100644 index 00000000..9a57a6a2 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntityUnitTest.java @@ -0,0 +1,20 @@ +package net.hostsharing.hsadminng.hs.hosting.contact; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class HsHostingContactEntityUnitTest { + + @Test + void toStringReturnsNullForNullContact() { + final HsHostingContactEntity givenContact = null; + assertThat("" + givenContact).isEqualTo("null"); + } + + @Test + void toStringReturnsCaption() { + final var givenContact = HsHostingContactEntity.builder().caption("given caption").build(); + assertThat("" + givenContact).isEqualTo("contact(caption='given caption')"); + } +} -- 2.39.2 From 9c43610e7c8d79cad2b766007651eebae27e7268 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 20 Jun 2024 15:35:50 +0200 Subject: [PATCH 2/5] fix ArchitectureTest --- .../java/net/hostsharing/hsadminng/arch/ArchitectureTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java index df26279d..9c73c9b9 100644 --- a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java +++ b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java @@ -58,6 +58,7 @@ public class ArchitectureTest { "..hs.booking.project", "..hs.booking.item", "..hs.booking.item.validators", + "..hs.hosting.contact", "..hs.hosting.asset", "..hs.hosting.asset.validators", "..errors", @@ -150,7 +151,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 -- 2.39.2 From cb29730810012d9120f37cd7fda9f677fe1761e8 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 20 Jun 2024 16:04:40 +0200 Subject: [PATCH 3/5] add alertContact to REST Schema, patcher and acceptance-test --- .../asset/HsHostingAssetController.java | 7 ++++- .../hosting/asset/HsHostingAssetEntity.java | 2 +- .../asset/HsHostingAssetEntityPatcher.java | 17 +++++++++++- .../hs-hosting/hs-hosting-asset-schemas.yaml | 10 +++++++ ...sHostingAssetControllerAcceptanceTest.java | 26 +++++++++++++++++-- .../HsHostingAssetEntityPatcherUnitTest.java | 26 ++++++++++++++++--- 6 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java index 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 a99b4f2a..dfd1d363 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 @@ -100,7 +100,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "alarmcontactuuid") - private HsHostingContactEntity alarmContactUuid; + private HsHostingContactEntity alarmContact; @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name="parentassetuuid", referencedColumnName="uuid") 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..adbcdcaf 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,22 @@ package net.hostsharing.hsadminng.hs.hosting.asset; +import net.hostsharing.hsadminng.hs.hosting.contact.HsHostingContactEntity; 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 jakarta.persistence.EntityManager; import java.util.Optional; +import java.util.UUID; 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 +26,15 @@ public class HsHostingAssetEntityPatcher implements EntityPatcher entity.getConfig().patch(KeyValueMap.from(resource.getConfig()))); + OptionalFromJson.of(resource.getAlarmContactUuid()).ifPresent(newValue -> { + verifyNotNull(newValue, "alarmContact"); + entity.setAlarmContact(em.getReference(HsHostingContactEntity.class, newValue)); + }); + } + + private void verifyNotNull(final UUID newValue, final String propertyName) { + if (newValue == null) { + throw new IllegalArgumentException("property '" + propertyName + "' must not be 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/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index 84fe1627..d2885cfd 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..e36755c8 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.contact.HsHostingContactEntity; 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; @@ -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 HsHostingContactEntity givenInitialContact = HsHostingContactEntity.builder() + .uuid(UUID.randomUUID()) + .build(); private static final String INITIAL_CAPTION = "initial caption"; private static final String PATCHED_CAPTION = "patched caption"; @@ -56,10 +60,12 @@ 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(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(HsHostingContactEntity.class), any())).thenAnswer(invocation -> + HsHostingContactEntity.builder().uuid(invocation.getArgument(1)).build()); } @Override @@ -69,6 +75,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 +86,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< @Override protected HsHostingAssetEntityPatcher createPatcher(final HsHostingAssetEntity server) { - return new HsHostingAssetEntityPatcher(server); + return new HsHostingAssetEntityPatcher(em, server); } @Override @@ -96,7 +103,18 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< PATCH_CONFIG, HsHostingAssetEntity::putConfig, PATCHED_CONFIG) + .notNullable(), + new JsonNullableProperty<>( + "alarmContact", + HsHostingAssetPatchResource::setAlarmContactUuid, + PATCHED_CONTACT_UUID, + HsHostingAssetEntity::setAlarmContact, + newContact(PATCHED_CONTACT_UUID)) .notNullable() ); } + + static HsHostingContactEntity newContact(final UUID uuid) { + return HsHostingContactEntity.builder().uuid(uuid).build(); + } } -- 2.39.2 From 1ba760e9a9f04825ed1e3be011f02066b0db2e17 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 20 Jun 2024 17:19:22 +0200 Subject: [PATCH 4/5] replace HsHostingContactEntity with HsOfficeContactEntity --- .../hosting/asset/HsHostingAssetEntity.java | 3 +- .../asset/HsHostingAssetEntityPatcher.java | 4 +- .../contact/HsHostingContactEntity.java | 57 ------------------- ...sHostingAssetControllerAcceptanceTest.java | 2 +- .../HsHostingAssetEntityPatcherUnitTest.java | 14 ++--- .../HsHostingContactEntityUnitTest.java | 20 ------- 6 files changed, 10 insertions(+), 90 deletions(-) delete mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntity.java delete mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntityUnitTest.java 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 dfd1d363..4891258f 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,7 +8,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; -import net.hostsharing.hsadminng.hs.hosting.contact.HsHostingContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; @@ -100,7 +99,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "alarmcontactuuid") - private HsHostingContactEntity alarmContact; + private HsOfficeContactEntity alarmContact; @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name="parentassetuuid", referencedColumnName="uuid") 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 adbcdcaf..753dc8fa 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,7 +1,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset; -import net.hostsharing.hsadminng.hs.hosting.contact.HsHostingContactEntity; 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; @@ -28,7 +28,7 @@ public class HsHostingAssetEntityPatcher implements EntityPatcher entity.getConfig().patch(KeyValueMap.from(resource.getConfig()))); OptionalFromJson.of(resource.getAlarmContactUuid()).ifPresent(newValue -> { verifyNotNull(newValue, "alarmContact"); - entity.setAlarmContact(em.getReference(HsHostingContactEntity.class, newValue)); + entity.setAlarmContact(em.getReference(HsOfficeContactEntity.class, newValue)); }); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntity.java deleted file mode 100644 index 8d14865b..00000000 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntity.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.hostsharing.hsadminng.hs.hosting.contact; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import lombok.*; -import lombok.experimental.FieldNameConstants; -import net.hostsharing.hsadminng.errors.DisplayName; -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.*; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static net.hostsharing.hsadminng.stringify.Stringify.stringify; - -@Entity -@Table(name = "hs_office_contact_rv") -@Getter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@FieldNameConstants -@DisplayName("Contact") -public class HsHostingContactEntity implements Stringifyable, RbacObject { - - private static Stringify toString = stringify(HsHostingContactEntity.class, "contact") - .withProp(HsHostingContactEntity.Fields.caption, HsHostingContactEntity::getCaption) - .withProp(HsHostingContactEntity.Fields.emailAddresses, HsHostingContactEntity::getEmailAddresses); - - @Id - private UUID uuid; - - @Version - private int version; - - @Column(name = "caption") - private String caption; - - @Builder.Default - @Setter(AccessLevel.NONE) - @Type(JsonType.class) - @Column(name = "emailaddresses") - private Map emailAddresses = new HashMap<>(); - - @Override - public String toString() { - return toString.apply(this); - } - - @Override - public String toShortString() { - return caption; - } -} 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 d2885cfd..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 @@ -477,7 +477,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup 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}')"); + "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; 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 e36755c8..0514d2b7 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.contact.HsHostingContactEntity; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource; +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; @@ -48,7 +48,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< entry("SSD", 256), entry("MEM", 64) ); - final HsHostingContactEntity givenInitialContact = HsHostingContactEntity.builder() + final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder() .uuid(UUID.randomUUID()) .build(); @@ -60,12 +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(HsHostingContactEntity.class), any())).thenAnswer(invocation -> - HsHostingContactEntity.builder().uuid(invocation.getArgument(1)).build()); + lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> + HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); } @Override @@ -114,7 +112,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< ); } - static HsHostingContactEntity newContact(final UUID uuid) { - return HsHostingContactEntity.builder().uuid(uuid).build(); + static HsOfficeContactEntity newContact(final UUID uuid) { + return HsOfficeContactEntity.builder().uuid(uuid).build(); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntityUnitTest.java deleted file mode 100644 index 9a57a6a2..00000000 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/contact/HsHostingContactEntityUnitTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.hostsharing.hsadminng.hs.hosting.contact; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class HsHostingContactEntityUnitTest { - - @Test - void toStringReturnsNullForNullContact() { - final HsHostingContactEntity givenContact = null; - assertThat("" + givenContact).isEqualTo("null"); - } - - @Test - void toStringReturnsCaption() { - final var givenContact = HsHostingContactEntity.builder().caption("given caption").build(); - assertThat("" + givenContact).isEqualTo("contact(caption='given caption')"); - } -} -- 2.39.2 From 3a1d883dd988f4d2aef68bb4ba5f4310d04028ca Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 21 Jun 2024 12:00:28 +0200 Subject: [PATCH 5/5] amendmends according to Code-Review --- .../hs/hosting/asset/HsHostingAssetEntity.java | 2 +- .../asset/HsHostingAssetEntityPatcher.java | 17 ++++++----------- .../7013-hs-hosting-asset-rbac.sql | 5 +++-- .../hsadminng/arch/ArchitectureTest.java | 5 +++-- .../HsHostingAssetEntityPatcherUnitTest.java | 1 - 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntity.java index 4891258f..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 @@ -142,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", "assignedToAssetUuid", "alarmContactUUid") + .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(), 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 753dc8fa..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 @@ -8,7 +8,6 @@ import net.hostsharing.hsadminng.mapper.OptionalFromJson; import jakarta.persistence.EntityManager; import java.util.Optional; -import java.util.UUID; public class HsHostingAssetEntityPatcher implements EntityPatcher { @@ -26,15 +25,11 @@ public class HsHostingAssetEntityPatcher implements EntityPatcher entity.getConfig().patch(KeyValueMap.from(resource.getConfig()))); - OptionalFromJson.of(resource.getAlarmContactUuid()).ifPresent(newValue -> { - verifyNotNull(newValue, "alarmContact"); - entity.setAlarmContact(em.getReference(HsOfficeContactEntity.class, newValue)); - }); - } - - private void verifyNotNull(final UUID newValue, final String propertyName) { - if (newValue == null) { - throw new IllegalArgumentException("property '" + propertyName + "' must not be null"); - } + 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/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 8a8b8c42..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 @@ -120,7 +120,8 @@ create or replace procedure updateRbacRulesForHsHostingAsset( language plpgsql as $$ begin - if NEW.assignedToAssetUuid is distinct from OLD.assignedToAssetUuid then + 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; @@ -277,7 +278,7 @@ call generateRbacRestrictedView('hs_hosting_asset', caption = new.caption, config = new.config, assignedToAssetUuid = new.assignedToAssetUuid, - alarmContactUUid = new.alarmContactUUid + 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 9c73c9b9..f626a3ed 100644 --- a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java +++ b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java @@ -58,7 +58,6 @@ public class ArchitectureTest { "..hs.booking.project", "..hs.booking.item", "..hs.booking.item.validators", - "..hs.hosting.contact", "..hs.hosting.asset", "..hs.hosting.asset.validators", "..errors", @@ -197,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/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityPatcherUnitTest.java index 0514d2b7..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 @@ -108,7 +108,6 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase< PATCHED_CONTACT_UUID, HsHostingAssetEntity::setAlarmContact, newContact(PATCHED_CONTACT_UUID)) - .notNullable() ); } -- 2.39.2