introduce-hosting-module #46

Merged
hsh-michaelhoennig merged 11 commits from introduce-hosting-module into master 2024-04-23 11:14:49 +02:00
13 changed files with 550 additions and 180 deletions
Showing only changes of commit f8caf43cd0 - Show all commits

View File

@ -18,8 +18,6 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@RestController @RestController
public class HsHostingServerController implements HsHostingServersApi { public class HsHostingServerController implements HsHostingServersApi {
@ -42,7 +40,7 @@ public class HsHostingServerController implements HsHostingServersApi {
final var entities = serverRepo.findAllByDebitorUuid(debitorUuid); final var entities = serverRepo.findAllByDebitorUuid(debitorUuid);
final var resources = mapper.mapList(entities, HsServerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var resources = mapper.mapList(entities, HsServerResource.class);
return ResponseEntity.ok(resources); return ResponseEntity.ok(resources);
} }
@ -65,7 +63,7 @@ public class HsHostingServerController implements HsHostingServersApi {
.path("/api/hs/hosting/servers/{id}") .path("/api/hs/hosting/servers/{id}")
.buildAndExpand(saved.getUuid()) .buildAndExpand(saved.getUuid())
.toUri(); .toUri();
final var mapped = mapper.map(saved, HsServerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var mapped = mapper.map(saved, HsServerResource.class);
return ResponseEntity.created(uri).body(mapped); return ResponseEntity.created(uri).body(mapped);
} }
@ -81,7 +79,7 @@ public class HsHostingServerController implements HsHostingServersApi {
final var result = serverRepo.findByUuid(serverUuid); final var result = serverRepo.findByUuid(serverUuid);
return result return result
.map(serverEntity -> ResponseEntity.ok( .map(serverEntity -> ResponseEntity.ok(
mapper.map(serverEntity, HsServerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER))) mapper.map(serverEntity, HsServerResource.class)))
.orElseGet(() -> ResponseEntity.notFound().build()); .orElseGet(() -> ResponseEntity.notFound().build());
} }
@ -114,20 +112,12 @@ public class HsHostingServerController implements HsHostingServersApi {
new HsHostingServerEntityPatcher(current).apply(body); new HsHostingServerEntityPatcher(current).apply(body);
final var saved = serverRepo.save(current); final var saved = serverRepo.save(current);
final var mapped = mapper.map(saved, HsServerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var mapped = mapper.map(saved, HsServerResource.class);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }
final BiConsumer<HsHostingServerEntity, HsServerResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setValidFrom(entity.getValidity().lower());
if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1));
}
};
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final BiConsumer<HsServerInsertResource, HsHostingServerEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsServerInsertResource, HsHostingServerEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo()));
entity.putConfig(KeyValueMap.from(resource.getConfig())); entity.putConfig(KeyValueMap.from(resource.getConfig()));
}; };
} }

View File

@ -1,8 +1,6 @@
package net.hostsharing.hsadminng.hs.hosting.server; package net.hostsharing.hsadminng.hs.hosting.server;
import io.hypersistence.utils.hibernate.type.json.JsonType; import io.hypersistence.utils.hibernate.type.json.JsonType;
import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@ -28,15 +26,11 @@ import jakarta.persistence.Table;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import jakarta.persistence.Version; import jakarta.persistence.Version;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE;
@ -44,6 +38,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.UPDATE; 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.OWNER; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.OWNER;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn;
@ -61,7 +56,6 @@ public class HsHostingServerEntity implements Stringifyable, RbacObject {
private static Stringify<HsHostingServerEntity> stringify = stringify(HsHostingServerEntity.class) private static Stringify<HsHostingServerEntity> stringify = stringify(HsHostingServerEntity.class)
.withProp(e -> e.getBookingItem().toShortString()) .withProp(e -> e.getBookingItem().toShortString())
.withProp(e -> e.getValidity().asString())
.withProp(HsHostingServerEntity::getCaption) .withProp(HsHostingServerEntity::getCaption)
.withProp(HsHostingServerEntity::getConfig) .withProp(HsHostingServerEntity::getConfig)
.quotedValues(false); .quotedValues(false);
@ -77,11 +71,6 @@ public class HsHostingServerEntity implements Stringifyable, RbacObject {
@JoinColumn(name = "bookingitemuuid") @JoinColumn(name = "bookingitemuuid")
private HsBookingItemEntity bookingItem; private HsBookingItemEntity bookingItem;
@Builder.Default
@Type(PostgreSQLRangeType.class)
@Column(name = "validity", columnDefinition = "daterange")
private Range<LocalDate> validity = Range.emptyRange(LocalDate.class);
@Column(name = "caption") @Column(name = "caption")
private String caption; private String caption;
@ -94,22 +83,6 @@ public class HsHostingServerEntity implements Stringifyable, RbacObject {
@Transient @Transient
private PatchableMapWrapper configWrapper; private PatchableMapWrapper configWrapper;
public void setValidFrom(final LocalDate validFrom) {
setValidity(toPostgresDateRange(validFrom, getValidTo()));
}
public void setValidTo(final LocalDate validTo) {
setValidity(toPostgresDateRange(getValidFrom(), validTo));
}
public LocalDate getValidFrom() {
return lowerInclusiveFromPostgresDateRange(getValidity());
}
public LocalDate getValidTo() {
return upperInclusiveFromPostgresDateRange(getValidity());
}
public PatchableMapWrapper getConfig() { public PatchableMapWrapper getConfig() {
if ( configWrapper == null ) { if ( configWrapper == null ) {
configWrapper = new PatchableMapWrapper(config); configWrapper = new PatchableMapWrapper(config);
@ -138,25 +111,26 @@ public class HsHostingServerEntity implements Stringifyable, RbacObject {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("server", HsHostingServerEntity.class) return rbacViewFor("server", HsHostingServerEntity.class)
.withIdentityView(SQL.query(""" .withIdentityView(SQL.query("""
SELECT server.uuid as uuid, bookingItemIV.idName || ':' || cleanIdentifier(server.caption) as idName SELECT server.uuid as uuid, bookingItemIV.idName || '-' || cleanIdentifier(server.caption) as idName
FROM hs_hosting_server server FROM hs_hosting_server server
JOIN hs_booking_item_iv bookingItemIV ON bookingItemIV.uuid = server.bookingItemUuid JOIN hs_booking_item_iv bookingItemIV ON bookingItemIV.uuid = server.bookingItemUuid
""")) """))
.withRestrictedViewOrderBy(SQL.expression("validity")) .withRestrictedViewOrderBy(SQL.expression("caption"))
.withUpdatableColumns("version", "caption", "validity", "config") .withUpdatableColumns("version", "caption", "config")
.importEntityAlias("bookingItem", HsBookingItemEntity.class, .importEntityAlias("bookingItem", HsBookingItemEntity.class,
dependsOnColumn("bookingItemUuid"), dependsOnColumn("bookingItemUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NOT_NULL) NOT_NULL)
.toRole("bookingItem", ADMIN).grantPermission(INSERT) .toRole("bookingItem", AGENT).grantPermission(INSERT)
.toRole("global", ADMIN).grantPermission(DELETE)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.incomingSuperRole("bookingItem", ADMIN); with.incomingSuperRole("bookingItem", ADMIN);
with.permission(DELETE);
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE); with.permission(UPDATE);
}) })
.createSubRole(ADMIN)
.createSubRole(TENANT, (with) -> { .createSubRole(TENANT, (with) -> {
with.outgoingSubRole("bookingItem", TENANT); with.outgoingSubRole("bookingItem", TENANT);
with.permission(SELECT); with.permission(SELECT);

View File

@ -7,7 +7,6 @@ import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import java.util.Optional; import java.util.Optional;
public class HsHostingServerEntityPatcher implements EntityPatcher<HsServerPatchResource> { public class HsHostingServerEntityPatcher implements EntityPatcher<HsServerPatchResource> {
private final HsHostingServerEntity entity; private final HsHostingServerEntity entity;
@ -22,7 +21,5 @@ public class HsHostingServerEntityPatcher implements EntityPatcher<HsServerPatch
.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.getValidTo())
.ifPresent(entity::setValidTo);
} }
} }

View File

@ -69,10 +69,10 @@ components:
BookingResources: BookingResources:
anyOf: anyOf:
- $ref: '#/components/schemas/ServerBookingResources' - $ref: '#/components/schemas/ManagedServerBookingResources'
- $ref: '#/components/schemas/ManagedWebspaceBookingResources' - $ref: '#/components/schemas/ManagedWebspaceBookingResources'
ServerBookingResources: ManagedServerBookingResources:
type: object type: object
properties: properties:
CPU: CPU:

View File

@ -11,18 +11,10 @@ components:
format: uuid format: uuid
caption: caption:
type: string type: string
validFrom:
type: string
format: date
validTo:
type: string
format: date
config: config:
$ref: '#/components/schemas/ServerConfiguration' $ref: '#/components/schemas/ServerConfiguration'
required: required:
- uuid - uuid
- validFrom
- validTo
- config - config
HsServerPatch: HsServerPatch:
@ -31,10 +23,6 @@ components:
caption: caption:
type: string type: string
nullable: true nullable: true
validTo:
type: string
format: date
nullable: true
config: config:
$ref: '#/components/schemas/ServerConfiguration' $ref: '#/components/schemas/ServerConfiguration'
@ -50,20 +38,11 @@ components:
minLength: 3 minLength: 3
maxLength: 80 maxLength: 80
nullable: false nullable: false
validFrom:
type: string
format: date
nullable: false
validTo:
type: string
format: date
nullable: true
config: config:
$ref: '#/components/schemas/ServerConfiguration' $ref: '#/components/schemas/ServerConfiguration'
required: required:
- caption - caption
- debitorUuid - debitorUuid
- validFrom
- config - config
additionalProperties: false additionalProperties: false
@ -72,11 +51,6 @@ components:
anyOf: anyOf:
- type: object - type: object
properties: properties:
caption:
type: string
minLength: 3
maxLength: 80
nullable: false
CPU: CPU:
type: integer type: integer
minimum: 1 minimum: 1

View File

@ -9,7 +9,6 @@ create table if not exists hs_hosting_server
uuid uuid unique references RbacObject (uuid), uuid uuid unique references RbacObject (uuid),
version int not null default 0, version int not null default 0,
bookingItemUuid uuid not null references hs_booking_item(uuid), bookingItemUuid uuid not null references hs_booking_item(uuid),
validity daterange not null,
caption varchar(80) not null, caption varchar(80) not null,
config jsonb not null config jsonb not null
); );

View File

@ -0,0 +1,305 @@
### rbac server
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph server["`**server**`"]
direction TB
style server fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph server:roles[ ]
style server:roles fill:#dd4901,stroke:white
role:server:OWNER[[server:OWNER]]
role:server:ADMIN[[server:ADMIN]]
role:server:TENANT[[server:TENANT]]
end
subgraph server:permissions[ ]
style server:permissions fill:#dd4901,stroke:white
perm:server:INSERT{{server:INSERT}}
perm:server:DELETE{{server:DELETE}}
perm:server:UPDATE{{server:UPDATE}}
perm:server:SELECT{{server:SELECT}}
end
end
subgraph bookingItem.debitor.debitorRel.anchorPerson["`**bookingItem.debitor.debitorRel.anchorPerson**`"]
direction TB
style bookingItem.debitor.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.debitorRel.anchorPerson:roles[ ]
style bookingItem.debitor.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.debitorRel.anchorPerson:OWNER[[bookingItem.debitor.debitorRel.anchorPerson:OWNER]]
role:bookingItem.debitor.debitorRel.anchorPerson:ADMIN[[bookingItem.debitor.debitorRel.anchorPerson:ADMIN]]
role:bookingItem.debitor.debitorRel.anchorPerson:REFERRER[[bookingItem.debitor.debitorRel.anchorPerson:REFERRER]]
end
end
subgraph bookingItem.debitor.partnerRel.contact["`**bookingItem.debitor.partnerRel.contact**`"]
direction TB
style bookingItem.debitor.partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.partnerRel.contact:roles[ ]
style bookingItem.debitor.partnerRel.contact:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.partnerRel.contact:OWNER[[bookingItem.debitor.partnerRel.contact:OWNER]]
role:bookingItem.debitor.partnerRel.contact:ADMIN[[bookingItem.debitor.partnerRel.contact:ADMIN]]
role:bookingItem.debitor.partnerRel.contact:REFERRER[[bookingItem.debitor.partnerRel.contact:REFERRER]]
end
end
subgraph 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 bookingItem.debitor.partnerRel["`**bookingItem.debitor.partnerRel**`"]
direction TB
style bookingItem.debitor.partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.partnerRel:roles[ ]
style bookingItem.debitor.partnerRel:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.partnerRel:OWNER[[bookingItem.debitor.partnerRel:OWNER]]
role:bookingItem.debitor.partnerRel:ADMIN[[bookingItem.debitor.partnerRel:ADMIN]]
role:bookingItem.debitor.partnerRel:AGENT[[bookingItem.debitor.partnerRel:AGENT]]
role:bookingItem.debitor.partnerRel:TENANT[[bookingItem.debitor.partnerRel:TENANT]]
end
end
subgraph bookingItem.debitor.partnerRel.anchorPerson["`**bookingItem.debitor.partnerRel.anchorPerson**`"]
direction TB
style bookingItem.debitor.partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.partnerRel.anchorPerson:roles[ ]
style bookingItem.debitor.partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.partnerRel.anchorPerson:OWNER[[bookingItem.debitor.partnerRel.anchorPerson:OWNER]]
role:bookingItem.debitor.partnerRel.anchorPerson:ADMIN[[bookingItem.debitor.partnerRel.anchorPerson:ADMIN]]
role:bookingItem.debitor.partnerRel.anchorPerson:REFERRER[[bookingItem.debitor.partnerRel.anchorPerson:REFERRER]]
end
end
subgraph bookingItem.debitorRel["`**bookingItem.debitorRel**`"]
direction TB
style bookingItem.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitorRel:roles[ ]
style bookingItem.debitorRel:roles fill:#99bcdb,stroke:white
role:bookingItem.debitorRel:OWNER[[bookingItem.debitorRel:OWNER]]
role:bookingItem.debitorRel:ADMIN[[bookingItem.debitorRel:ADMIN]]
role:bookingItem.debitorRel:AGENT[[bookingItem.debitorRel:AGENT]]
role:bookingItem.debitorRel:TENANT[[bookingItem.debitorRel:TENANT]]
end
end
subgraph bookingItem.debitorRel.anchorPerson["`**bookingItem.debitorRel.anchorPerson**`"]
direction TB
style bookingItem.debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitorRel.anchorPerson:roles[ ]
style bookingItem.debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:bookingItem.debitorRel.anchorPerson:OWNER[[bookingItem.debitorRel.anchorPerson:OWNER]]
role:bookingItem.debitorRel.anchorPerson:ADMIN[[bookingItem.debitorRel.anchorPerson:ADMIN]]
role:bookingItem.debitorRel.anchorPerson:REFERRER[[bookingItem.debitorRel.anchorPerson:REFERRER]]
end
end
subgraph bookingItem.debitor.partnerRel.holderPerson["`**bookingItem.debitor.partnerRel.holderPerson**`"]
direction TB
style bookingItem.debitor.partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.partnerRel.holderPerson:roles[ ]
style bookingItem.debitor.partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.partnerRel.holderPerson:OWNER[[bookingItem.debitor.partnerRel.holderPerson:OWNER]]
role:bookingItem.debitor.partnerRel.holderPerson:ADMIN[[bookingItem.debitor.partnerRel.holderPerson:ADMIN]]
role:bookingItem.debitor.partnerRel.holderPerson:REFERRER[[bookingItem.debitor.partnerRel.holderPerson:REFERRER]]
end
end
subgraph bookingItem.debitorRel.contact["`**bookingItem.debitorRel.contact**`"]
direction TB
style bookingItem.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitorRel.contact:roles[ ]
style bookingItem.debitorRel.contact:roles fill:#99bcdb,stroke:white
role:bookingItem.debitorRel.contact:OWNER[[bookingItem.debitorRel.contact:OWNER]]
role:bookingItem.debitorRel.contact:ADMIN[[bookingItem.debitorRel.contact:ADMIN]]
role:bookingItem.debitorRel.contact:REFERRER[[bookingItem.debitorRel.contact:REFERRER]]
end
end
subgraph bookingItem.debitorRel.holderPerson["`**bookingItem.debitorRel.holderPerson**`"]
direction TB
style bookingItem.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitorRel.holderPerson:roles[ ]
style bookingItem.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:bookingItem.debitorRel.holderPerson:OWNER[[bookingItem.debitorRel.holderPerson:OWNER]]
role:bookingItem.debitorRel.holderPerson:ADMIN[[bookingItem.debitorRel.holderPerson:ADMIN]]
role:bookingItem.debitorRel.holderPerson:REFERRER[[bookingItem.debitorRel.holderPerson:REFERRER]]
end
end
subgraph bookingItem.debitor.refundBankAccount["`**bookingItem.debitor.refundBankAccount**`"]
direction TB
style bookingItem.debitor.refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.refundBankAccount:roles[ ]
style bookingItem.debitor.refundBankAccount:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.refundBankAccount:OWNER[[bookingItem.debitor.refundBankAccount:OWNER]]
role:bookingItem.debitor.refundBankAccount:ADMIN[[bookingItem.debitor.refundBankAccount:ADMIN]]
role:bookingItem.debitor.refundBankAccount:REFERRER[[bookingItem.debitor.refundBankAccount:REFERRER]]
end
end
subgraph bookingItem.debitor.debitorRel.contact["`**bookingItem.debitor.debitorRel.contact**`"]
direction TB
style bookingItem.debitor.debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.debitorRel.contact:roles[ ]
style bookingItem.debitor.debitorRel.contact:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.debitorRel.contact:OWNER[[bookingItem.debitor.debitorRel.contact:OWNER]]
role:bookingItem.debitor.debitorRel.contact:ADMIN[[bookingItem.debitor.debitorRel.contact:ADMIN]]
role:bookingItem.debitor.debitorRel.contact:REFERRER[[bookingItem.debitor.debitorRel.contact:REFERRER]]
end
end
subgraph bookingItem.debitor["`**bookingItem.debitor**`"]
direction TB
style bookingItem.debitor fill:#99bcdb,stroke:#274d6e,stroke-width:8px
end
subgraph bookingItem.debitor.debitorRel.holderPerson["`**bookingItem.debitor.debitorRel.holderPerson**`"]
direction TB
style bookingItem.debitor.debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.debitorRel.holderPerson:roles[ ]
style bookingItem.debitor.debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.debitorRel.holderPerson:OWNER[[bookingItem.debitor.debitorRel.holderPerson:OWNER]]
role:bookingItem.debitor.debitorRel.holderPerson:ADMIN[[bookingItem.debitor.debitorRel.holderPerson:ADMIN]]
role:bookingItem.debitor.debitorRel.holderPerson:REFERRER[[bookingItem.debitor.debitorRel.holderPerson:REFERRER]]
end
end
subgraph bookingItem.debitor.debitorRel["`**bookingItem.debitor.debitorRel**`"]
direction TB
style bookingItem.debitor.debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bookingItem.debitor.debitorRel:roles[ ]
style bookingItem.debitor.debitorRel:roles fill:#99bcdb,stroke:white
role:bookingItem.debitor.debitorRel:OWNER[[bookingItem.debitor.debitorRel:OWNER]]
role:bookingItem.debitor.debitorRel:ADMIN[[bookingItem.debitor.debitorRel:ADMIN]]
role:bookingItem.debitor.debitorRel:AGENT[[bookingItem.debitor.debitorRel:AGENT]]
role:bookingItem.debitor.debitorRel:TENANT[[bookingItem.debitor.debitorRel:TENANT]]
end
end
%% granting roles to roles
role:global:ADMIN -.-> role:bookingItem.debitor.debitorRel.anchorPerson:OWNER
role:bookingItem.debitor.debitorRel.anchorPerson:OWNER -.-> role:bookingItem.debitor.debitorRel.anchorPerson:ADMIN
role:bookingItem.debitor.debitorRel.anchorPerson:ADMIN -.-> role:bookingItem.debitor.debitorRel.anchorPerson:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitor.debitorRel.holderPerson:OWNER
role:bookingItem.debitor.debitorRel.holderPerson:OWNER -.-> role:bookingItem.debitor.debitorRel.holderPerson:ADMIN
role:bookingItem.debitor.debitorRel.holderPerson:ADMIN -.-> role:bookingItem.debitor.debitorRel.holderPerson:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitor.debitorRel.contact:OWNER
role:bookingItem.debitor.debitorRel.contact:OWNER -.-> role:bookingItem.debitor.debitorRel.contact:ADMIN
role:bookingItem.debitor.debitorRel.contact:ADMIN -.-> role:bookingItem.debitor.debitorRel.contact:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitor.debitorRel:OWNER
role:bookingItem.debitor.debitorRel:OWNER -.-> role:bookingItem.debitor.debitorRel:ADMIN
role:bookingItem.debitor.debitorRel:ADMIN -.-> role:bookingItem.debitor.debitorRel:AGENT
role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.debitorRel:TENANT
role:bookingItem.debitor.debitorRel.contact:ADMIN -.-> role:bookingItem.debitor.debitorRel:TENANT
role:bookingItem.debitor.debitorRel:TENANT -.-> role:bookingItem.debitor.debitorRel.anchorPerson:REFERRER
role:bookingItem.debitor.debitorRel:TENANT -.-> role:bookingItem.debitor.debitorRel.holderPerson:REFERRER
role:bookingItem.debitor.debitorRel:TENANT -.-> role:bookingItem.debitor.debitorRel.contact:REFERRER
role:bookingItem.debitor.debitorRel.anchorPerson:ADMIN -.-> role:bookingItem.debitor.debitorRel:OWNER
role:bookingItem.debitor.debitorRel.holderPerson:ADMIN -.-> role:bookingItem.debitor.debitorRel:AGENT
role:global:ADMIN -.-> role:bookingItem.debitor.refundBankAccount:OWNER
role:bookingItem.debitor.refundBankAccount:OWNER -.-> role:bookingItem.debitor.refundBankAccount:ADMIN
role:bookingItem.debitor.refundBankAccount:ADMIN -.-> role:bookingItem.debitor.refundBankAccount:REFERRER
role:bookingItem.debitor.refundBankAccount:ADMIN -.-> role:bookingItem.debitor.debitorRel:AGENT
role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.refundBankAccount:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitor.partnerRel.anchorPerson:OWNER
role:bookingItem.debitor.partnerRel.anchorPerson:OWNER -.-> role:bookingItem.debitor.partnerRel.anchorPerson:ADMIN
role:bookingItem.debitor.partnerRel.anchorPerson:ADMIN -.-> role:bookingItem.debitor.partnerRel.anchorPerson:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitor.partnerRel.holderPerson:OWNER
role:bookingItem.debitor.partnerRel.holderPerson:OWNER -.-> role:bookingItem.debitor.partnerRel.holderPerson:ADMIN
role:bookingItem.debitor.partnerRel.holderPerson:ADMIN -.-> role:bookingItem.debitor.partnerRel.holderPerson:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitor.partnerRel.contact:OWNER
role:bookingItem.debitor.partnerRel.contact:OWNER -.-> role:bookingItem.debitor.partnerRel.contact:ADMIN
role:bookingItem.debitor.partnerRel.contact:ADMIN -.-> role:bookingItem.debitor.partnerRel.contact:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitor.partnerRel:OWNER
role:bookingItem.debitor.partnerRel:OWNER -.-> role:bookingItem.debitor.partnerRel:ADMIN
role:bookingItem.debitor.partnerRel:ADMIN -.-> role:bookingItem.debitor.partnerRel:AGENT
role:bookingItem.debitor.partnerRel:AGENT -.-> role:bookingItem.debitor.partnerRel:TENANT
role:bookingItem.debitor.partnerRel.contact:ADMIN -.-> role:bookingItem.debitor.partnerRel:TENANT
role:bookingItem.debitor.partnerRel:TENANT -.-> role:bookingItem.debitor.partnerRel.anchorPerson:REFERRER
role:bookingItem.debitor.partnerRel:TENANT -.-> role:bookingItem.debitor.partnerRel.holderPerson:REFERRER
role:bookingItem.debitor.partnerRel:TENANT -.-> role:bookingItem.debitor.partnerRel.contact:REFERRER
role:bookingItem.debitor.partnerRel.anchorPerson:ADMIN -.-> role:bookingItem.debitor.partnerRel:OWNER
role:bookingItem.debitor.partnerRel.holderPerson:ADMIN -.-> role:bookingItem.debitor.partnerRel:AGENT
role:bookingItem.debitor.partnerRel:ADMIN -.-> role:bookingItem.debitor.debitorRel:ADMIN
role:bookingItem.debitor.partnerRel:AGENT -.-> role:bookingItem.debitor.debitorRel:AGENT
role:bookingItem.debitor.debitorRel:AGENT -.-> role:bookingItem.debitor.partnerRel:TENANT
role:global:ADMIN -.-> role:bookingItem.debitorRel.anchorPerson:OWNER
role:bookingItem.debitorRel.anchorPerson:OWNER -.-> role:bookingItem.debitorRel.anchorPerson:ADMIN
role:bookingItem.debitorRel.anchorPerson:ADMIN -.-> role:bookingItem.debitorRel.anchorPerson:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitorRel.holderPerson:OWNER
role:bookingItem.debitorRel.holderPerson:OWNER -.-> role:bookingItem.debitorRel.holderPerson:ADMIN
role:bookingItem.debitorRel.holderPerson:ADMIN -.-> role:bookingItem.debitorRel.holderPerson:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitorRel.contact:OWNER
role:bookingItem.debitorRel.contact:OWNER -.-> role:bookingItem.debitorRel.contact:ADMIN
role:bookingItem.debitorRel.contact:ADMIN -.-> role:bookingItem.debitorRel.contact:REFERRER
role:global:ADMIN -.-> role:bookingItem.debitorRel:OWNER
role:bookingItem.debitorRel:OWNER -.-> role:bookingItem.debitorRel:ADMIN
role:bookingItem.debitorRel:ADMIN -.-> role:bookingItem.debitorRel:AGENT
role:bookingItem.debitorRel:AGENT -.-> role:bookingItem.debitorRel:TENANT
role:bookingItem.debitorRel.contact:ADMIN -.-> role:bookingItem.debitorRel:TENANT
role:bookingItem.debitorRel:TENANT -.-> role:bookingItem.debitorRel.anchorPerson:REFERRER
role:bookingItem.debitorRel:TENANT -.-> role:bookingItem.debitorRel.holderPerson:REFERRER
role:bookingItem.debitorRel:TENANT -.-> role:bookingItem.debitorRel.contact:REFERRER
role:bookingItem.debitorRel.anchorPerson:ADMIN -.-> role:bookingItem.debitorRel:OWNER
role:bookingItem.debitorRel.holderPerson:ADMIN -.-> role:bookingItem.debitorRel:AGENT
role:bookingItem.debitorRel:AGENT -.-> role:bookingItem:OWNER
role:bookingItem:OWNER -.-> role:bookingItem:ADMIN
role:bookingItem.debitorRel:AGENT -.-> role:bookingItem:ADMIN
role:bookingItem:ADMIN -.-> role:bookingItem:AGENT
role:bookingItem:AGENT -.-> role:bookingItem:TENANT
role:bookingItem:TENANT -.-> role:bookingItem.debitorRel:TENANT
role:bookingItem:ADMIN ==> role:server:OWNER
role:server:OWNER ==> role:server:ADMIN
role:server:ADMIN ==> role:server:TENANT
role:server:TENANT ==> role:bookingItem:TENANT
%% granting permissions to roles
role:bookingItem:AGENT ==> perm:server:INSERT
role:server:OWNER ==> perm:server:DELETE
role:server:ADMIN ==> perm:server:UPDATE
role:server:TENANT ==> perm:server:SELECT
```

View File

@ -0,0 +1,172 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-hosting-server-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_hosting_server');
--//
-- ============================================================================
--changeset hs-hosting-server-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsHostingServer', 'hs_hosting_server');
--//
-- ============================================================================
--changeset hs-hosting-server-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsHostingServer(
NEW hs_hosting_server
)
language plpgsql as $$
declare
newBookingItem hs_booking_item;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_booking_item WHERE uuid = NEW.bookingItemUuid INTO newBookingItem;
assert newBookingItem.uuid is not null, format('newBookingItem must not be null for NEW.bookingItemUuid = %s', NEW.bookingItemUuid);
perform createRoleWithGrants(
hsHostingServerOWNER(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[hsBookingItemADMIN(newBookingItem)]
);
perform createRoleWithGrants(
hsHostingServerADMIN(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsHostingServerOWNER(NEW)]
);
perform createRoleWithGrants(
hsHostingServerTENANT(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsHostingServerADMIN(NEW)],
outgoingSubRoles => array[hsBookingItemTENANT(newBookingItem)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_hosting_server row.
*/
create or replace function insertTriggerForHsHostingServer_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsHostingServer(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsHostingServer_tg
after insert on hs_hosting_server
for each row
execute procedure insertTriggerForHsHostingServer_tf();
--//
-- ============================================================================
--changeset hs-hosting-server-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_hosting_server permissions for the related hs_booking_item rows.
*/
do language plpgsql $$
declare
row hs_booking_item;
begin
call defineContext('create INSERT INTO hs_hosting_server permissions for the related hs_booking_item rows');
FOR row IN SELECT * FROM hs_booking_item
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_hosting_server'),
hsBookingItemAGENT(row));
END LOOP;
END;
$$;
/**
Adds hs_hosting_server INSERT permission to specified role of new hs_booking_item rows.
*/
create or replace function hs_hosting_server_hs_booking_item_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_hosting_server'),
hsBookingItemAGENT(NEW));
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_hs_hosting_server_hs_booking_item_insert_tg
after insert on hs_booking_item
for each row
execute procedure hs_hosting_server_hs_booking_item_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_hosting_server,
where the check is performed by a direct role.
A direct role is a role depending on a foreign key directly available in the NEW row.
*/
create or replace function hs_hosting_server_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_hosting_server not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_hosting_server_insert_permission_check_tg
before insert on hs_hosting_server
for each row
when ( not hasInsertPermission(NEW.bookingItemUuid, 'INSERT', 'hs_hosting_server') )
execute procedure hs_hosting_server_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-hosting-server-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromQuery('hs_hosting_server',
$idName$
SELECT server.uuid as uuid, bookingItemIV.idName || '-' || cleanIdentifier(server.caption) as idName
FROM hs_hosting_server server
JOIN hs_booking_item_iv bookingItemIV ON bookingItemIV.uuid = server.bookingItemUuid
$idName$);
--//
-- ============================================================================
--changeset hs-hosting-server-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_hosting_server',
$orderBy$
caption
$orderBy$,
$updates$
version = new.version,
caption = new.caption,
config = new.config
$updates$);
--//

View File

@ -36,10 +36,10 @@ begin
raise notice 'creating test hosting-server: %', givenPartnerNumber::text || givenDebitorSuffix::text; raise notice 'creating test hosting-server: %', givenPartnerNumber::text || givenDebitorSuffix::text;
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor; raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
insert insert
into hs_hosting_server (uuid, bookingitemuuid, caption, validity, config) into hs_hosting_server (uuid, bookingitemuuid, caption, config)
values (uuid_generate_v4(), relatedBookingItem.uuid, 'some ManagedServer', daterange('20221001', null, '[]'), '{ "CPU": 2, "SDD": 512, "extra": 42 }'::jsonb), values (uuid_generate_v4(), relatedBookingItem.uuid, 'some ManagedServer', '{ "CPU": 2, "SDD": 512, "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedBookingItem.uuid, 'another CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb), (uuid_generate_v4(), relatedBookingItem.uuid, 'another CloudServer', '{ "CPU": 2, "HDD": 1024, "extra": 42 }'::jsonb),
(uuid_generate_v4(), relatedBookingItem.uuid, 'some Whatever', daterange('20240401', null, '[]'), '{ "CPU": 1, "SDD": 512, "HDD": 2048, "extra": 42 }'::jsonb); (uuid_generate_v4(), relatedBookingItem.uuid, 'some Whatever', '{ "CPU": 1, "SDD": 512, "HDD": 2048, "extra": 42 }'::jsonb);
end; $$; end; $$;
--// --//

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.hosting.server; package net.hostsharing.hsadminng.hs.hosting.server;
import io.hypersistence.utils.hibernate.type.range.Range;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
@ -16,7 +15,6 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -30,7 +28,7 @@ import static org.hamcrest.Matchers.matchesRegex;
classes = { HsadminNgApplication.class, JpaAttempt.class } classes = { HsadminNgApplication.class, JpaAttempt.class }
) )
@Transactional @Transactional
class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup { class HsHostingServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
@LocalServerPort @LocalServerPort
private Integer port; private Integer port;
@ -70,8 +68,6 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
[ [
{ {
"caption": "some ManagedServer", "caption": "some ManagedServer",
"validFrom": "2022-10-01",
"validTo": null,
"config": { "config": {
"CPU": 2, "CPU": 2,
"SDD": 512, "SDD": 512,
@ -80,8 +76,6 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
}, },
{ {
"caption": "another CloudServer", "caption": "another CloudServer",
"validFrom": "2023-01-15",
"validTo": "2024-04-14",
"config": { "config": {
"CPU": 2, "CPU": 2,
"HDD": 1024, "HDD": 1024,
@ -90,8 +84,6 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
}, },
{ {
"caption": "some Whatever", "caption": "some Whatever",
"validFrom": "2024-04-01",
"validTo": null,
"config": { "config": {
"CPU": 1, "CPU": 1,
"HDD": 2048, "HDD": 2048,
@ -122,8 +114,7 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
{ {
"bookingItemUuid": "%s", "bookingItemUuid": "%s",
"caption": "some new CloudServer", "caption": "some new CloudServer",
"config": { "CPU": 3, "extra": 42 }, "config": { "CPU": 3, "extra": 42 }
"validFrom": "2024-04-17"
} }
""".formatted(givenBookingItem.getUuid())) """.formatted(givenBookingItem.getUuid()))
.port(port) .port(port)
@ -135,8 +126,7 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"caption": "some new CloudServer", "caption": "some new CloudServer",
"config": { "CPU": 3, "extra": 42 }, "config": { "CPU": 3, "extra": 42 }
"validFrom": "2024-04-17"
} }
""")) """))
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/servers/[^/]*")) .header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/servers/[^/]*"))
@ -172,8 +162,6 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"caption": "some ManagedServer", "caption": "some ManagedServer",
"validFrom": "2022-10-01",
"validTo": null,
"config": { "config": {
"CPU": 2, "CPU": 2,
"SDD": 512, "SDD": 512,
@ -221,8 +209,6 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"caption": "some ManagedServer", "caption": "some ManagedServer",
"validFrom": "2022-10-01",
"validTo": null,
"config": { "config": {
"CPU": 2, "CPU": 2,
"SDD": 512, "SDD": 512,
@ -247,8 +233,6 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"validFrom": "2020-06-05",
"validTo": "2022-12-31",
"config": { "config": {
"CPU": "4", "CPU": "4",
"HDD": null, "HDD": null,
@ -264,9 +248,7 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"caption": "some test-booking", "caption": "some test-server",
"validFrom": "2022-11-01",
"validTo": "2022-12-31",
"config": { "config": {
"CPU": "4", "CPU": "4",
"SSD": "4096", "SSD": "4096",
@ -278,10 +260,8 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
// finally, the server is actually updated // finally, the server is actually updated
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
assertThat(serverRepo.findByUuid(givenServer.getUuid())).isPresent().get() assertThat(serverRepo.findByUuid(givenServer.getUuid())).isPresent().get()
.matches(mandate -> { .matches(server -> {
assertThat(mandate.getBookingItem().toShortString()).isEqualTo("D-1000111:some CloudServer"); assertThat(server.toString()).isEqualTo("HsHostingServerEntity(D-1000111:some CloudServer, some test-server, { CPU: 4, SSD: 4096, something: 1 })");
assertThat(mandate.getValidFrom()).isEqualTo("2022-11-01");
assertThat(mandate.getValidTo()).isEqualTo("2022-12-31");
return true; return true;
}); });
} }
@ -341,10 +321,8 @@ class HsServerControllerAcceptanceTest extends ContextBasedTestWithCleanup {
final var newServer = HsHostingServerEntity.builder() final var newServer = HsHostingServerEntity.builder()
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.bookingItem(givenBookingItem("First", "some CloudServer")) .bookingItem(givenBookingItem("First", "some CloudServer"))
.caption("some test-booking") .caption("some test-server")
.config(Map.ofEntries(resources)) .config(Map.ofEntries(resources))
.validity(Range.closedOpen(
LocalDate.parse("2022-11-01"), LocalDate.parse("2023-03-31")))
.build(); .build();
return serverRepo.save(newServer); return serverRepo.save(newServer);

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.hosting.server; package net.hostsharing.hsadminng.hs.hosting.server;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsServerPatchResource; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsServerPatchResource;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.mapper.KeyValueMap; import net.hostsharing.hsadminng.mapper.KeyValueMap;
@ -12,7 +11,6 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import java.time.LocalDate;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -33,8 +31,6 @@ class HsHostingServerEntityPatcherUnitTest 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 LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-04-15");
private static final LocalDate PATCHED_VALID_TO = LocalDate.parse("2022-12-31");
private static final Map<String, Object> INITIAL_CONFIG = patchMap( private static final Map<String, Object> INITIAL_CONFIG = patchMap(
entry("CPU", 1), entry("CPU", 1),
@ -73,7 +69,6 @@ class HsHostingServerEntityPatcherUnitTest 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.setValidity(Range.closedInfinite(GIVEN_VALID_FROM));
return entity; return entity;
} }
@ -101,12 +96,7 @@ class HsHostingServerEntityPatcherUnitTest extends PatchUnitTestBase<
PATCH_CONFIG, PATCH_CONFIG,
HsHostingServerEntity::putConfig, HsHostingServerEntity::putConfig,
PATCHED_CONFIG) PATCHED_CONFIG)
.notNullable(), .notNullable()
new JsonNullableProperty<>(
"validto",
HsServerPatchResource::setValidTo,
PATCHED_VALID_TO,
HsHostingServerEntity::setValidTo)
); );
} }
} }

View File

@ -2,17 +2,13 @@ package net.hostsharing.hsadminng.hs.hosting.server;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.util.Map; import java.util.Map;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_BOOKING_ITEM; import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_BOOKING_ITEM;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsHostingServerEntityUnitTest { class HsHostingServerEntityUnitTest {
public static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-01-01");
public static final LocalDate GIVEN_VALID_TO = LocalDate.parse("2030-12-31");
final HsHostingServerEntity givenServer = HsHostingServerEntity.builder() final HsHostingServerEntity givenServer = HsHostingServerEntity.builder()
.bookingItem(TEST_BOOKING_ITEM) .bookingItem(TEST_BOOKING_ITEM)
@ -21,7 +17,6 @@ class HsHostingServerEntityUnitTest {
entry("CPUs", 2), entry("CPUs", 2),
entry("SSD-storage", 512), entry("SSD-storage", 512),
entry("HDD-storage", 2048))) entry("HDD-storage", 2048)))
.validity(toPostgresDateRange(GIVEN_VALID_FROM, GIVEN_VALID_TO))
.build(); .build();
@Test @Test
@ -29,7 +24,7 @@ class HsHostingServerEntityUnitTest {
final var result = givenServer.toString(); final var result = givenServer.toString();
assertThat(result).isEqualTo( assertThat(result).isEqualTo(
"HsServerEntity(D-1000100:test booking item, [2020-01-01,2031-01-01), some caption, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })"); "HsHostingServerEntity(D-1000100:test booking item, some caption, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })");
} }
@Test @Test
@ -38,20 +33,4 @@ class HsHostingServerEntityUnitTest {
assertThat(result).isEqualTo("D-1000100:test booking item:some caption"); assertThat(result).isEqualTo("D-1000100:test booking item:some caption");
} }
@Test
void settingValidFromKeepsValidTo() {
givenServer.setValidFrom(LocalDate.parse("2023-12-31"));
assertThat(givenServer.getValidFrom()).isEqualTo(LocalDate.parse("2023-12-31"));
assertThat(givenServer.getValidTo()).isEqualTo(GIVEN_VALID_TO);
}
@Test
void settingValidToKeepsValidFrom() {
givenServer.setValidTo(LocalDate.parse("2024-12-31"));
assertThat(givenServer.getValidFrom()).isEqualTo(GIVEN_VALID_FROM);
assertThat(givenServer.getValidTo()).isEqualTo(LocalDate.parse("2024-12-31"));
}
} }

View File

@ -1,6 +1,5 @@
package net.hostsharing.hsadminng.hs.hosting.server; package net.hostsharing.hsadminng.hs.hosting.server;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
@ -21,7 +20,6 @@ import org.springframework.orm.jpa.JpaSystemException;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -77,8 +75,6 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
final var newServer = HsHostingServerEntity.builder() final var newServer = HsHostingServerEntity.builder()
.bookingItem(givenBookingItem) .bookingItem(givenBookingItem)
.caption("some new booking server") .caption("some new booking server")
.validity(Range.closedOpen(
LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
.build(); .build();
return toCleanup(serverRepo.save(newServer)); return toCleanup(serverRepo.save(newServer));
}); });
@ -105,8 +101,6 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
final var newServer = HsHostingServerEntity.builder() final var newServer = HsHostingServerEntity.builder()
.bookingItem(givenBookingItem) .bookingItem(givenBookingItem)
.caption("some new booking server") .caption("some new booking server")
.validity(Range.closedOpen(
LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
.build(); .build();
return toCleanup(serverRepo.save(newServer)); return toCleanup(serverRepo.save(newServer));
}); });
@ -115,27 +109,27 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
final var all = rawRoleRepo.findAll(); final var all = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from( assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:ADMIN", "hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:ADMIN",
"hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:OWNER", "hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:OWNER",
"hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:TENANT")); "hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:TENANT"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())) assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(fromFormatted( .containsExactlyInAnyOrder(fromFormatted(
initialGrantNames, initialGrantNames,
// global-admin // global-admin
"{ grant perm:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:DELETE to role:global#global:ADMIN by system and assume }", "{ grant perm:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:DELETE to role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:OWNER by system and assume }",
// owner // owner
"{ grant perm:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:UPDATE to role:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:OWNER by system and assume }", "{ grant perm:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:UPDATE to role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:ADMIN by system and assume }",
"{ grant role:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:OWNER to role:hs_booking_item#D-1000111:someCloudServer:ADMIN by system and assume }",
// admin // admin
"{ grant role:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:ADMIN to role:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:OWNER by system and assume }", "{ grant role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:ADMIN to role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:OWNER by system and assume }",
"{ grant role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:OWNER to role:hs_booking_item#D-1000111-someCloudServer:ADMIN by system and assume }",
// tenant // tenant
"{ grant role:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:TENANT to role:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:ADMIN by system and assume }", "{ grant role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:TENANT to role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:ADMIN by system and assume }",
"{ grant perm:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:SELECT to role:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:TENANT by system and assume }", "{ grant perm:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:SELECT to role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:TENANT by system and assume }",
"{ grant role:hs_booking_item#D-1000111:someCloudServer:TENANT to role:hs_hosting_server#D-1000111:someCloudServer:somenewbookingserver:TENANT by system and assume }", "{ grant role:hs_booking_item#D-1000111-someCloudServer:TENANT to role:hs_hosting_server#D-1000111-someCloudServer-somenewbookingserver:TENANT by system and assume }",
null)); null));
} }
@ -162,9 +156,9 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
// then // then
allTheseServersAreReturned( allTheseServersAreReturned(
result, result,
"HsServerEntity(D-1000212:some PrivateCloud, [2022-10-01,), some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })", "HsHostingServerEntity(D-1000212:some PrivateCloud, another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })",
"HsServerEntity(D-1000212:some PrivateCloud, [2023-01-15,2024-04-15), another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })", "HsHostingServerEntity(D-1000212:some PrivateCloud, some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })",
"HsServerEntity(D-1000212:some PrivateCloud, [2024-04-01,), some Whatever, { CPU: 1, HDD: 2048, SDD: 512, extra: 42 })"); "HsHostingServerEntity(D-1000212:some PrivateCloud, some Whatever, { CPU: 1, HDD: 2048, SDD: 512, extra: 42 })");
} }
@Test @Test
@ -179,9 +173,9 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
// then: // then:
exactlyTheseServersAreReturned( exactlyTheseServersAreReturned(
result, result,
"HsServerEntity(D-1000111:some PrivateCloud, [2022-10-01,), some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })", "HsHostingServerEntity(D-1000111:some PrivateCloud, another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })",
"HsServerEntity(D-1000111:some PrivateCloud, [2023-01-15,2024-04-15), another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })", "HsHostingServerEntity(D-1000111:some PrivateCloud, some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })",
"HsServerEntity(D-1000111:some PrivateCloud, [2024-04-01,), some Whatever, { CPU: 1, HDD: 2048, SDD: 512, extra: 42 })"); "HsHostingServerEntity(D-1000111:some PrivateCloud, some Whatever, { CPU: 1, HDD: 2048, SDD: 512, extra: 42 })");
} }
} }
@ -191,7 +185,7 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
@Test @Test
public void hostsharingAdmin_canUpdateArbitraryServer() { public void hostsharingAdmin_canUpdateArbitraryServer() {
// given // given
final var givenServerUuid = givenSomeTemporaryServer(1000111).getUuid(); final var givenServerUuid = givenSomeTemporaryServer("First").getUuid();
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -200,8 +194,6 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
foundServer.getConfig().put("CPUs", 2); foundServer.getConfig().put("CPUs", 2);
foundServer.getConfig().remove("SSD-storage"); foundServer.getConfig().remove("SSD-storage");
foundServer.getConfig().put("HSD-storage", 2048); foundServer.getConfig().put("HSD-storage", 2048);
foundServer.setValidity(Range.closedOpen(
LocalDate.parse("2019-05-17"), LocalDate.parse("2023-01-01")));
return toCleanup(serverRepo.save(foundServer)); return toCleanup(serverRepo.save(foundServer));
}); });
@ -227,7 +219,7 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
public void globalAdmin_withoutAssumedRole_canDeleteAnyServer() { public void globalAdmin_withoutAssumedRole_canDeleteAnyServer() {
// given // given
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
final var givenServer = givenSomeTemporaryServer(1000111); final var givenServer = givenSomeTemporaryServer("First");
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -244,10 +236,10 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
} }
@Test @Test
public void nonGlobalAdmin_canNotDeleteTheirRelatedServer() { public void relatedOwner_canDeleteTheirRelatedServer() {
// given // given
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
final var givenServer = givenSomeTemporaryServer(1000111); final var givenServer = givenSomeTemporaryServer("First");
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -257,6 +249,28 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
serverRepo.deleteByUuid(givenServer.getUuid()); serverRepo.deleteByUuid(givenServer.getUuid());
}); });
// then
result.assertSuccessful();
assertThat(jpaAttempt.transacted(() -> {
context("superuser-fran@hostsharing.net", null);
return serverRepo.findByUuid(givenServer.getUuid());
}).assertSuccessful().returnedValue()).isEmpty();
}
@Test
public void relatedAdmin_canNotDeleteTheirRelatedServer() {
// given
context("superuser-alex@hostsharing.net", null);
final var givenServer = givenSomeTemporaryServer("First");
// when
final var result = jpaAttempt.transacted(() -> {
context("person-FirbySusan@example.com", "hs_hosting_server#D-1000111-someCloudServer-sometempbookingserver:ADMIN");
assertThat(serverRepo.findByUuid(givenServer.getUuid())).isPresent();
serverRepo.deleteByUuid(givenServer.getUuid());
});
// then // then
result.assertExceptionWithRootCauseMessage( result.assertExceptionWithRootCauseMessage(
JpaSystemException.class, JpaSystemException.class,
@ -273,7 +287,7 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll())); final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll()));
final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll())); final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll()));
final var givenServer = givenSomeTemporaryServer(1000111); final var givenServer = givenSomeTemporaryServer("First");
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -308,15 +322,13 @@ class HsHostingServerRepositoryIntegrationTest extends ContextBasedTestWithClean
"[creating hosting-server test-data 1000313, hs_hosting_server, INSERT]"); "[creating hosting-server test-data 1000313, hs_hosting_server, INSERT]");
} }
private HsHostingServerEntity givenSomeTemporaryServer(final int debitorNumber) { private HsHostingServerEntity givenSomeTemporaryServer(final String debitorName) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenBookingItem = givenBookingItem("First", "some CloudServer"); final var givenBookingItem = givenBookingItem(debitorName, "some CloudServer");
final var newServer = HsHostingServerEntity.builder() final var newServer = HsHostingServerEntity.builder()
.bookingItem(givenBookingItem) .bookingItem(givenBookingItem)
.caption("some temp booking server") .caption("some temp booking server")
.validity(Range.closedOpen(
LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
.config(Map.ofEntries( .config(Map.ofEntries(
entry("CPUs", 1), entry("CPUs", 1),
entry("SSD-storage", 256))) entry("SSD-storage", 256)))