add optional alarm-contact to hosting-asset (#64)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #64
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-06-21 12:02:07 +02:00
parent d157730de7
commit 9418303b7c
17 changed files with 544 additions and 286 deletions

View File

@ -16,7 +16,9 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -26,6 +28,9 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAss
@RestController @RestController
public class HsHostingAssetController implements HsHostingAssetsApi { public class HsHostingAssetController implements HsHostingAssetsApi {
@PersistenceContext
private EntityManager em;
@Autowired @Autowired
private Context context; private Context context;
@ -119,7 +124,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
final var current = assetRepo.findByUuid(assetUuid).orElseThrow(); 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 saved = validated(assetRepo.save(current));
final var mapped = mapper.map(saved, HsHostingAssetResource.class); final var mapped = mapper.map(saved, HsHostingAssetResource.class);

View File

@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -48,6 +49,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.UPDATE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT; 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.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.Role.TENANT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
@ -95,6 +97,10 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private HsHostingAssetType type; private HsHostingAssetType type;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "alarmcontactuuid")
private HsOfficeContactEntity alarmContact;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY) @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name="parentassetuuid", referencedColumnName="uuid") @JoinColumn(name="parentassetuuid", referencedColumnName="uuid")
private List<HsHostingAssetEntity> subHostingAssets; private List<HsHostingAssetEntity> subHostingAssets;
@ -136,7 +142,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
return rbacViewFor("asset", HsHostingAssetEntity.class) return rbacViewFor("asset", HsHostingAssetEntity.class)
.withIdentityView(SQL.projection("identifier")) .withIdentityView(SQL.projection("identifier"))
.withRestrictedViewOrderBy(SQL.expression("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? .toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
.importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(), .importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(),
@ -155,6 +161,11 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NULLABLE) NULLABLE)
.importEntityAlias("alarmContact", HsOfficeContactEntity.class, usingDefaultCase(),
dependsOnColumn("alarmContactUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.incomingSuperRole("bookingItem", ADMIN); with.incomingSuperRole("bookingItem", ADMIN);
with.incomingSuperRole("parentAsset", ADMIN); with.incomingSuperRole("parentAsset", ADMIN);
@ -167,13 +178,15 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
}) })
.createSubRole(AGENT, (with) -> { .createSubRole(AGENT, (with) -> {
with.outgoingSubRole("assignedToAsset", TENANT); with.outgoingSubRole("assignedToAsset", TENANT);
with.outgoingSubRole("alarmContact", REFERRER);
}) })
.createSubRole(TENANT, (with) -> { .createSubRole(TENANT, (with) -> {
with.outgoingSubRole("bookingItem", TENANT); with.outgoingSubRole("bookingItem", TENANT);
with.outgoingSubRole("parentAsset", TENANT); with.outgoingSubRole("parentAsset", TENANT);
with.incomingSuperRole("alarmContact", ADMIN);
with.permission(SELECT); 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 { public static void main(String[] args) throws IOException {

View File

@ -1,17 +1,21 @@
package net.hostsharing.hsadminng.hs.hosting.asset; package net.hostsharing.hsadminng.hs.hosting.asset;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource; 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.EntityPatcher;
import net.hostsharing.hsadminng.mapper.KeyValueMap; import net.hostsharing.hsadminng.mapper.KeyValueMap;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import jakarta.persistence.EntityManager;
import java.util.Optional; import java.util.Optional;
public class HsHostingAssetEntityPatcher implements EntityPatcher<HsHostingAssetPatchResource> { public class HsHostingAssetEntityPatcher implements EntityPatcher<HsHostingAssetPatchResource> {
private final EntityManager em;
private final HsHostingAssetEntity entity; private final HsHostingAssetEntity entity;
public HsHostingAssetEntityPatcher(final HsHostingAssetEntity entity) { HsHostingAssetEntityPatcher(final EntityManager em, final HsHostingAssetEntity entity) {
this.em = em;
this.entity = entity; this.entity = entity;
} }
@ -21,5 +25,11 @@ public class HsHostingAssetEntityPatcher implements EntityPatcher<HsHostingAsset
.ifPresent(entity::setCaption); .ifPresent(entity::setCaption);
Optional.ofNullable(resource.getConfig()) Optional.ofNullable(resource.getConfig())
.ifPresent(r -> entity.getConfig().patch(KeyValueMap.from(resource.getConfig()))); .ifPresent(r -> entity.getConfig().patch(KeyValueMap.from(resource.getConfig())));
OptionalFromJson.of(resource.getAlarmContactUuid())
// HOWTO: patch nullable JSON resource uuid to an ntity reference
.ifPresent(newValue -> entity.setAlarmContact(
Optional.ofNullable(newValue)
.map(uuid -> em.getReference(HsOfficeContactEntity.class, newValue))
.orElse(null)));
} }
} }

View File

@ -32,6 +32,8 @@ components:
type: string type: string
caption: caption:
type: string type: string
alarmContact:
$ref: '../hs-office/hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact'
config: config:
$ref: '#/components/schemas/HsHostingAssetConfiguration' $ref: '#/components/schemas/HsHostingAssetConfiguration'
required: required:
@ -46,6 +48,10 @@ components:
caption: caption:
type: string type: string
nullable: true nullable: true
alarmContactUuid:
type: string
format: uuid
nullable: true
config: config:
$ref: '#/components/schemas/HsHostingAssetConfiguration' $ref: '#/components/schemas/HsHostingAssetConfiguration'
@ -72,6 +78,10 @@ components:
minLength: 3 minLength: 3
maxLength: 80 maxLength: 80
nullable: false nullable: false
alarmContactUuid:
type: string
format: uuid
nullable: true
config: config:
$ref: '#/components/schemas/HsHostingAssetConfiguration' $ref: '#/components/schemas/HsHostingAssetConfiguration'
required: required:

View File

@ -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
```

View File

@ -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$);
--//

View File

@ -33,6 +33,7 @@ create table if not exists hs_hosting_asset
identifier varchar(80) not null, identifier varchar(80) not null,
caption varchar(80), caption varchar(80),
config jsonb not null, 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 constraint chk_hs_hosting_asset_has_booking_item_or_parent_asset
check (bookingItemUuid is not null or parentAssetUuid is not null) check (bookingItemUuid is not null or parentAssetUuid is not null)

View File

@ -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
```

View File

@ -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
```

View File

@ -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
```

View File

@ -6,6 +6,19 @@ This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manua
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB 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**`"] subgraph asset["`**asset**`"]
direction TB direction TB
style asset fill:#dd4901,stroke:#274d6e,stroke-width:8px style asset fill:#dd4901,stroke:#274d6e,stroke-width:8px
@ -25,6 +38,7 @@ subgraph asset["`**asset**`"]
perm:asset:INSERT{{asset:INSERT}} perm:asset:INSERT{{asset:INSERT}}
perm:asset:DELETE{{asset:DELETE}} perm:asset:DELETE{{asset:DELETE}}
perm:asset:UPDATE{{asset:UPDATE}} perm:asset:UPDATE{{asset:UPDATE}}
perm:asset:SELECT{{asset:SELECT}}
end end
end end
@ -39,16 +53,58 @@ subgraph assignedToAsset["`**assignedToAsset**`"]
end end
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 %% 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: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:ADMIN ==> role:asset:AGENT
role:asset:AGENT ==> role:assignedToAsset:TENANT role:asset:AGENT ==> role:assignedToAsset:TENANT
role:asset:AGENT ==> role:alarmContact:REFERRER
role:asset:AGENT ==> role:asset:TENANT 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 %% granting permissions to roles
role:global:ADMIN ==> perm:asset:INSERT role:global:ADMIN ==> perm:asset:INSERT
role:parentAsset:ADMIN ==> perm:asset:INSERT
role:asset:OWNER ==> perm:asset:DELETE role:asset:OWNER ==> perm:asset:DELETE
role:asset:ADMIN ==> perm:asset:UPDATE role:asset:ADMIN ==> perm:asset:UPDATE
role:asset:TENANT ==> perm:asset:SELECT
``` ```

View File

@ -32,6 +32,7 @@ create or replace procedure buildRbacSystemForHsHostingAsset(
declare declare
newBookingItem hs_booking_item; newBookingItem hs_booking_item;
newAssignedToAsset hs_hosting_asset; newAssignedToAsset hs_hosting_asset;
newAlarmContact hs_office_contact;
newParentAsset hs_hosting_asset; newParentAsset hs_hosting_asset;
begin begin
@ -41,6 +42,8 @@ begin
SELECT * FROM hs_hosting_asset WHERE uuid = NEW.assignedToAssetUuid INTO newAssignedToAsset; 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; SELECT * FROM hs_hosting_asset WHERE uuid = NEW.parentAssetUuid INTO newParentAsset;
perform createRoleWithGrants( perform createRoleWithGrants(
@ -63,14 +66,17 @@ begin
perform createRoleWithGrants( perform createRoleWithGrants(
hsHostingAssetAGENT(NEW), hsHostingAssetAGENT(NEW),
incomingSuperRoles => array[hsHostingAssetADMIN(NEW)], incomingSuperRoles => array[hsHostingAssetADMIN(NEW)],
outgoingSubRoles => array[hsHostingAssetTENANT(newAssignedToAsset)] outgoingSubRoles => array[
hsHostingAssetTENANT(newAssignedToAsset),
hsOfficeContactREFERRER(newAlarmContact)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsHostingAssetTENANT(NEW), hsHostingAssetTENANT(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[ incomingSuperRoles => array[
hsHostingAssetAGENT(NEW), hsHostingAssetAGENT(NEW),
hsHostingAssetTENANT(newAssignedToAsset)], hsOfficeContactADMIN(newAlarmContact)],
outgoingSubRoles => array[ outgoingSubRoles => array[
hsBookingItemTENANT(newBookingItem), hsBookingItemTENANT(newBookingItem),
hsHostingAssetTENANT(newParentAsset)] hsHostingAssetTENANT(newParentAsset)]
@ -99,6 +105,48 @@ execute procedure insertTriggerForHsHostingAsset_tf();
--// --//
-- ============================================================================
--changeset hs-hosting-asset-rbac-update-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
*/
create or replace procedure updateRbacRulesForHsHostingAsset(
OLD hs_hosting_asset,
NEW hs_hosting_asset
)
language plpgsql as $$
begin
if NEW.assignedToAssetUuid is distinct from OLD.assignedToAssetUuid
or NEW.alarmContactUuid is distinct from OLD.alarmContactUuid then
delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid;
call buildRbacSystemForHsHostingAsset(NEW);
end if;
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_hosting_asset row.
*/
create or replace function updateTriggerForHsHostingAsset_tf()
returns trigger
language plpgsql
strict as $$
begin
call updateRbacRulesForHsHostingAsset(OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerForHsHostingAsset_tg
after update on hs_hosting_asset
for each row
execute procedure updateTriggerForHsHostingAsset_tf();
--//
-- ============================================================================ -- ============================================================================
--changeset hs-hosting-asset-rbac-GRANTING-INSERT-PERMISSION:1 endDelimiter:--// --changeset hs-hosting-asset-rbac-GRANTING-INSERT-PERMISSION:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -146,49 +194,6 @@ create trigger z_new_hs_hosting_asset_grants_insert_to_global_tg
for each row for each row
execute procedure new_hs_hosting_asset_grants_insert_to_global_tf(); 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 permission to hs_hosting_asset ----------------------------
-- Granting INSERT INTO hs_hosting_asset permissions to specified role of pre-existing hs_hosting_asset rows slipped, -- Granting INSERT INTO hs_hosting_asset permissions to specified role of pre-existing hs_hosting_asset rows slipped,
@ -234,10 +239,6 @@ begin
if isGlobalAdmin() then if isGlobalAdmin() then
return NEW; return NEW;
end if; 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 -- check INSERT permission via direct foreign key: NEW.parentAssetUuid
if hasInsertPermission(NEW.parentAssetUuid, 'hs_hosting_asset') then if hasInsertPermission(NEW.parentAssetUuid, 'hs_hosting_asset') then
return NEW; return NEW;
@ -275,7 +276,9 @@ call generateRbacRestrictedView('hs_hosting_asset',
$updates$ $updates$
version = new.version, version = new.version,
caption = new.caption, caption = new.caption,
config = new.config config = new.config,
assignedToAssetUuid = new.assignedToAssetUuid,
alarmContactUuid = new.alarmContactUuid
$updates$); $updates$);
--// --//

View File

@ -150,7 +150,8 @@ public class ArchitectureTest {
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage( .resideInAnyPackage(
"..hs.booking.(*)..", "..hs.booking.(*)..",
"..hs.hosting.(*).." "..hs.hosting.(*)..",
"..hs.validation" // TODO.impl: Some Validators need to be refactored to booking package.
); );
@ArchTest @ArchTest
@ -195,7 +196,9 @@ public class ArchitectureTest {
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
"..hs.office.migration.."); "..hs.office.migration..",
"..hs.hosting.asset.."
);
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")

View File

@ -97,9 +97,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll()); final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream() final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll());
.map(s -> s.replace("hs_office_", ""))
.toList();
// when // when
attempt(em, () -> { attempt(em, () -> {
@ -124,7 +122,6 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
"hs_booking_item#somenewbookingitem:OWNER", "hs_booking_item#somenewbookingitem:OWNER",
"hs_booking_item#somenewbookingitem:TENANT")); "hs_booking_item#somenewbookingitem:TENANT"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())) assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(fromFormatted( .containsExactlyInAnyOrder(fromFormatted(
initialGrantNames, initialGrantNames,
@ -138,7 +135,6 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
// admin // admin
"{ grant perm:hs_booking_item#somenewbookingitem:UPDATE to role:hs_booking_item#somenewbookingitem:ADMIN by system and assume }", "{ 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 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 // agent
"{ grant role:hs_booking_item#somenewbookingitem:AGENT to role:hs_booking_item#somenewbookingitem:ADMIN by system and assume }", "{ grant role:hs_booking_item#somenewbookingitem:AGENT to role:hs_booking_item#somenewbookingitem:ADMIN by system and assume }",

View File

@ -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.HsBookingItemRepository;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRepository; 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.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
@ -54,6 +56,9 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
@Autowired @Autowired
HsOfficeDebitorRepository debitorRepo; HsOfficeDebitorRepository debitorRepo;
@Autowired
HsOfficeContactRepository contactRepo;
@Autowired @Autowired
JpaAttempt jpaAttempt; JpaAttempt jpaAttempt;
@ -425,6 +430,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
final var givenAsset = givenSomeTemporaryHostingAsset("2001", MANAGED_SERVER, 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)); 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 RestAssured // @formatter:off
.given() .given()
@ -432,13 +438,14 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"alarmContactUuid": "%s",
"config": { "config": {
"monit_max_ssd_usage": 85, "monit_max_ssd_usage": 85,
"monit_max_hdd_usage": null, "monit_max_hdd_usage": null,
"monit_min_free_ssd": 5 "monit_min_free_ssd": 5
} }
} }
""") """.formatted(alarmContactUuid))
.port(port) .port(port)
.when() .when()
.patch("http://localhost/api/hs/hosting/assets/" + givenAsset.getUuid()) .patch("http://localhost/api/hs/hosting/assets/" + givenAsset.getUuid())
@ -450,6 +457,11 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"identifier": "vm2001", "identifier": "vm2001",
"caption": "some test-asset", "caption": "some test-asset",
"alarmContact": {
"uuid": "%s",
"caption": "second contact",
"emailAddresses": { "main": "contact-admin@secondcontact.example.com" }
},
"config": { "config": {
"monit_max_cpu_usage": 90, "monit_max_cpu_usage": 90,
"monit_max_ram_usage": 70, "monit_max_ram_usage": 70,
@ -457,12 +469,15 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"monit_min_free_ssd": 5 "monit_min_free_ssd": 5
} }
} }
""")); // @formatter:on """.formatted(alarmContactUuid)));
// @formatter:on
// finally, the asset is actually updated // finally, the asset is actually updated
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get() assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get()
.matches(asset -> { .matches(asset -> {
assertThat(asset.getAlarmContact().toString()).isEqualTo(
"contact(caption='second contact', emailAddresses='{ main: contact-admin@secondcontact.example.com }')");
assertThat(asset.getConfig().toString()).isEqualTo( 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 }"); "{ monit_max_cpu_usage: 90, monit_max_ram_usage: 70, monit_max_ssd_usage: 85, monit_min_free_ssd: 5 }");
return true; 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 @Nested
@Order(5) @Order(5)
class DeleteAsset { class DeleteAsset {

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.hosting.asset; package net.hostsharing.hsadminng.hs.hosting.asset;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.mapper.KeyValueMap; import net.hostsharing.hsadminng.mapper.KeyValueMap;
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach; 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 INITIAL_BOOKING_ITEM_UUID = UUID.randomUUID();
private static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID();
private static final Map<String, Object> INITIAL_CONFIG = patchMap( private static final Map<String, Object> INITIAL_CONFIG = patchMap(
entry("CPU", 1), entry("CPU", 1),
@ -47,6 +48,9 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase<
entry("SSD", 256), entry("SSD", 256),
entry("MEM", 64) entry("MEM", 64)
); );
final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder()
.uuid(UUID.randomUUID())
.build();
private static final String INITIAL_CAPTION = "initial caption"; private static final String INITIAL_CAPTION = "initial caption";
private static final String PATCHED_CAPTION = "patched caption"; private static final String PATCHED_CAPTION = "patched caption";
@ -56,10 +60,10 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase<
@BeforeEach @BeforeEach
void initMocks() { 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 -> lenient().when(em.getReference(eq(HsHostingAssetEntity.class), any())).thenAnswer(invocation ->
HsHostingAssetEntity.builder().uuid(invocation.getArgument(1)).build()); HsHostingAssetEntity.builder().uuid(invocation.getArgument(1)).build());
lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation ->
HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build());
} }
@Override @Override
@ -69,6 +73,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase<
entity.setBookingItem(TEST_BOOKING_ITEM); entity.setBookingItem(TEST_BOOKING_ITEM);
entity.getConfig().putAll(KeyValueMap.from(INITIAL_CONFIG)); entity.getConfig().putAll(KeyValueMap.from(INITIAL_CONFIG));
entity.setCaption(INITIAL_CAPTION); entity.setCaption(INITIAL_CAPTION);
entity.setAlarmContact(givenInitialContact);
return entity; return entity;
} }
@ -79,7 +84,7 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase<
@Override @Override
protected HsHostingAssetEntityPatcher createPatcher(final HsHostingAssetEntity server) { protected HsHostingAssetEntityPatcher createPatcher(final HsHostingAssetEntity server) {
return new HsHostingAssetEntityPatcher(server); return new HsHostingAssetEntityPatcher(em, server);
} }
@Override @Override
@ -96,7 +101,17 @@ class HsHostingAssetEntityPatcherUnitTest extends PatchUnitTestBase<
PATCH_CONFIG, PATCH_CONFIG,
HsHostingAssetEntity::putConfig, HsHostingAssetEntity::putConfig,
PATCHED_CONFIG) PATCHED_CONFIG)
.notNullable() .notNullable(),
new JsonNullableProperty<>(
"alarmContact",
HsHostingAssetPatchResource::setAlarmContactUuid,
PATCHED_CONTACT_UUID,
HsHostingAssetEntity::setAlarmContact,
newContact(PATCHED_CONTACT_UUID))
); );
} }
static HsOfficeContactEntity newContact(final UUID uuid) {
return HsOfficeContactEntity.builder().uuid(uuid).build();
}
} }

View File

@ -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_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#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 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)); null));
} }