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')"); + } +}