move Parter+Debitor person+contact to related Relationsship (#20)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #20
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-03-28 12:15:13 +01:00
parent 4572c6bda0
commit d3ca2b7e23
117 changed files with 3995 additions and 5287 deletions

View File

@ -0,0 +1,82 @@
*(this is just a scribbled draft, that's why it's still in German)*
### *Schema-F* für Permissions, Rollen und Grants
Permissions, Rollen und Grants werden in den INSERT/UPDATE/DELETE-Triggern von Geschäftsobjekten erzeugt und gelöscht. Das Löschen erfolgt meistens automatisch über das zugehörige RbacObject, die INSERT- und UPDATE-Trigger müssen jedoch in *pl/pgsql* ausprogrammiert werden.
Das folgende Schema soll dabei unterstützen, die richtigen Permissions, Rollen und Grants festzulegen.
An einigen Stellen ist vom *Initiator* die Rede. Als *Initiator* gilt derjenige User, der die Operation (INSERT oder UPDATE) durchführt bzw. dessen primary assumed Rol. (TODO: bisher gibt es nur assumed roles, das Konzept einer primary assumed Role müsste noch eingeführt werden, derzeit nehmen wir dafür immer den `globalAdmin()`. Bevor Kunden aber selbst Objekte anlegen können, muss das geklärt sein.)
#### Typ Root: Objekte, welche nur eine Spezialisierung bzw. Zusatzdaten für andere Objekte bereitstellen (z.B. Partner für Relations vom Typ Partner oder Partner Details für Partner)
Objektorientiert gedacht, enthalten solche Objekte die Zusatzdaten einer Subklasse; die Daten im Partner erweitern also eine Relation vom Typ `partner`.
- Dann muss dieses Objekt zeitlich nach dem Objekt erzeugt werden, auf dass es sich bezieht, also z.B. zeitlich nach der Relation.
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
- Es werden **keine** Rollen für dieses Objekt erzeugt.
- Statt eigener Rollen werden die o.g. Permissions passenden Rollen des Hauptobjekts zugewiesen (granted) bzw. aus denen entfernt (revoked).
- Handelt es sich um Zusatzdaten zum Zwecke der Spezialisierung, dann z.B. so:
- Delete (\*) <-- Owner des Hauptobjektes
- Edit <-- **Admin** des Hauptobjektes
- View <-- Agent des Hauptobjektes
- Handelt es sich um Zusatzdaten, für die sich Edit-Rechte delegieren lassen sollen (wie im Falle der Partner-Details eines Partners), dann z.B. so:
- Delete (\*) <-- Owner des Hauptobjektes
- Edit <-- **Agent** des Hauptobjektes
- View <-- Agent des Hauptobjektes
- Für die Rollenzuordnung zwischen referenzierten Objekten gilt:
- Für Objekte vom Typ Root werden die Rollen des zugehörigen Aggregator-Objektes verwendet.
- Gibt es Referenzen auf hierarchisch verbundene Objekte (z.B. Debitor.refundBankAccount) gilt folgende Faustregel:
***Nach oben absteigen, nach unten halten oder aufsteigen.*** An einem fachlich übergeordneten Objekt wird also eine niedrigere Rolle (z.B. Debitor-admin -> Partner.agent), einem fachlich untergeordneten Objekt eine gleichwertige Rolle (z.B. Partner.admin -> Debitor.admin) zugewiesen oder sogar aufgestiegen (Debitor.admin -> Package.tenant).
- Für Referenzen zwischen Objekten, die nicht hierarchisch zueinander stehen (z.B. Debitor und Bankverbindung), wird auf beiden seiten abgestiegen (also Debitor.admin -> BankAccount.referrer und BankAccount.admin -> Debitor.tenant).
Anmerkung: Der Typ-Begriff *Root* bezieht sich auf die Rolle im fachlichen Datenmodell. Im Bezug auf den Teilgraphen eines fachlichen Kontexts ist dies auch eine Wurzel im Sinne der Graphentheorie. Aber in anderen fachlichen Kontexten können auch diese Objekte von anderen Teilgraphen referenziert werden und werden dann zum inneren Knoten.
#### Typ Aggregator: Objekte, welche weitere Objekte zusammenfassen (z.B. Relation fasst zwei Persons und einen Contact zusammen)
Solche Objekte verweisen üblicherweise auf Objekte vom Typ Leaf und werden oft von Objekten des Typs Root referenziert.
- Es werden i.d.R. folgende Rollen für diese Objekte erzeugt:
- Owner, Admin, Agent, Tenent(, Guest?)
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
- Die Permissions werden den Rollen sinnvoll zugewiesen, z.B.:
- Owner -> Delete (\*)
- Admin --> Edit
- Tenant (oder ggf. Guest) --> View
- Außerdem werden folgende Grants erstellt bzw. entzogen:
- Initiator --> Owner
- Owner --> Admin
- Admin --> Referrer
- Admins der referenzierten Objekte werden Agent des Aggregators
- Tenants des Aggregators werden Referrer der referenzierten Objekte
### Typ Leaf: Handelt es sich um ein Objekt, welches (außer zur Modellierung separater Permissions) keine Unterobjekte enthält (z.B. Person, Customer)?
Solche Objekte werden üblicherweise von Objekten des Typs Aggregator, manchmal auch von Objekten des Typs Root, referenziert.
- Es werden i.d.R. folgende Rollen für diese Objekte erzeugt:
- Owner, Admin, Referrer
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
- Die Permissions werden den Rollen sinnvoll zugewiesen, z.B.:
- Delete (\*) <-- Owner
- Edit <-- Admin
- View <-- Referrer
- Außerdem werden folgende Grants erstellt bzw. entzogen:
- Owner --> Admin
- Admin --> Referrer
```mermaid
flowchart LR
subgraph partnerDetails
direction TB
style partnerDetails fill:#eee
perm:partnerDetails.*{{partnerDetails.*}}
role:partnerDetails.edit{{partnerDetails.edit}}
role:partnerDetails.view{{partnerDetails.view}}
end
```

View File

@ -0,0 +1,29 @@
(this is just a scribbled idea, that's why it's still in German)
Ich habe mal wieder vom RBAC-System geträumt 🙈 Ok, im Halbschlaf darüber nachgedacht trifft es wohl besser. Und jetzt frage ich mich, ob wir viel zu kompliziert gedacht haben.
Bislang gingen wir ja davon aus, dass, wenn komplexe Entitäten (z.B. Partner) erzeugt werden, wir wir über den INSERT-Trigger den Rollen der verknüpften Entitäten (z.B. den Rollen der Personendaten des Partners) auch Rechte an den komplexeren Entitäten und umgekehrt geben müssen.
Da die komplexen Entitäten nur mit gewissen verbundenen Entitäten überhaupt sinnvoll nutzbar sind und diese daher über INNSER JOINs mitladen, könnte sonst auch nur jemand diese Entitäten, der auch die SELECT-Permission an den verküpften Entitäten hat.
Vor einigen Wochen hatten wir schon einmal darüber geredet, ob wir dieses Geflecht wirklich komplett durchplanen müssen, also über mehrere Stufen hinweg, oder ob sehr warscheinlich eh dieselben Leuten an den weiter entfernten Entitäten die nötien Rechte haben, weil dahinter dieselben User stehen. Also z.B. dass gewährleistet ist, dass jemand mit ADMIN-Recht an den Personendaten des Partners auch bis in die SEPA-Mandate eines Debitors hineinsehen kann.
Und nun gehe ich noch einen Schritt weiter: Könnte es nicht auch andersherum sein? Also wenn jemand z.B. SELECT-Recht am Partner hat, dass wir davon ausgehen können, dass derjenige auch die Partner-Personen- und Kontaktdaten sehen darf, und zwar implizit durch seine Partner-SELECT-Permission und ohne dass er explizit Rollen für diese Partner-Personen oder Kontaktdaten inne hat?
Im Halbschlaf kam mir nur die Idee, warum wir nicht einfach die komplexen JPA-Entitäten zwar auf die restricted View setzen, wie bisher, aber für die verknüpften Entitäten auf die direkten (bisher "Raw..." genannt) Entitäten gehen. Dann könnte jemand mit einer Rolle, welche die SELECT-Permission auf die komplexe JPA-Entität (z.B.) Partner inne hat, auch die dazugehörige Relation(ship) ["Relation" wurde vor kurzem auf kurz "Relation" umbenannt] und die wiederum dazu gehörigen Personen- und Kontaktdaten lesen, ohne dass in einem INSERT- und UPDATE-Trigger der Partner-Entität die ganzen Grants mit den verknüpften Entäten aufgebaut und aktualisiert werden müssen.
Beim Debitor ist das nämlich selbst mit Generator die Hölle, zumal eben auch Querverbindungen gegranted werden müssen, z.B. von der Debitor-Person zum Sema-Mandat - jedenfalls wenn man nicht Gefahr laufen wollte, dass jemand mit Admin-Rechten an der Partner-Person (also z.B. ein Repräsentant des Partners) die Sepa-Mandate der Debitoren gar nicht mehr sehen kann. Natürlich bräuchte man immer noch die Agent-Rolle am Partner und Debitor (evtl. repräsentiert durch die jeweils zugehörigen Relation - falls dieser Trick überhaupt noch nötig wäre), sowie ein Grant vom Partner-Agent auf den Debitor-Agent und vom Debitor-Agent auf die Sepa-Mandate-Admins, aber eben ohne filigran die ganzen Neben-Entäten (Personen- und Kontaktdaten von Partner und Debitor sowie Bank-Account) in jedem Trigger berücksichtigen zu müssen. Beim Refund-Bank-Account sogar besonders ätzend, weil der optional ist und dadurch zig "if ...refundBankAccountUuid is not null then ..." im Code enstehen (wenn der auch generiert ist).
Mit anderen Worten, um als Repräsentant eines Geschäftspartners auf den Bank-Account der Sepa-Mandate sehen zu dürfen, wird derzeut folgende Grant-Kette durchlaufen (bzw. eben noch nicht, weil es noch nicht funktioniert):
User -> Partner-Holder-Person:Admin -> Partner-Relation:Agent -> Debitor-Relation:Agent -> Sepa-Mandat:Admin -> BankAccount:Admin -> BankAccount:SELECT
Daraus würde:
User -> Partner-Relation:Agent -> Debitor-Relation:Agent -> Sepa-Mandat:Admin -> Sepa-Mandat:SELECT*
(*mit JOIN auf RawBankAccount, also implizitem Leserecht)
Das klingt zunächst nach nur einer marginalen Vereinfachung, die eigentlich Vereinfachung liegt aber im Erzeugen der Grants in den Triggern, denn da sind zudem noch Partner-Anchor-Person, Debitor-Holder- und Anchor-Person, Partner- und Debitor-Contact sowie der RefundBankAccount zu berücksichtigen. Und genau diese Grants würden großteils wegfallen, und durch implizite Persmissions über die JOINs auf die Raw-Tables ersetzt werden. Den refundBankAccound müssten wir dann, analog zu den Sepa-Mandataten, umgedreht modellieren, da den sonst
Man könnte das Ganze auch als "Entwicklung der Rechtestruktur für Hosting-Entitäten auf der obersten Ebene" (Manged Webspace, Managed Server, Cloud Server etc.) sehen, denn die hängen alle unter dem Mega-komplexen Debitor.

View File

@ -1,6 +1,6 @@
## *hsadmin-ng*'s Role-Based-Access-Management (RBAC) ## *hsadmin-ng*'s Role-Based-Access-Management (RBAC)
The requirements of *hsadmin-ng* include table-m row- and column-level-security for read and write access to business-objects. The requirements of *hsadmin-ng* include table-, row- and column-level-security for read and write access to business-objects.
More precisely, any access has to be controlled according to given rules depending on the accessing users, their roles and the accessed business-object. More precisely, any access has to be controlled according to given rules depending on the accessing users, their roles and the accessed business-object.
Further, roles and business-objects are hierarchical. Further, roles and business-objects are hierarchical.

View File

@ -33,8 +33,8 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable { public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficeBankAccountEntity> toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount") private static Stringify<HsOfficeBankAccountEntity> toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount")
.withIdProp(HsOfficeBankAccountEntity::getIban)
.withProp(Fields.holder, HsOfficeBankAccountEntity::getHolder) .withProp(Fields.holder, HsOfficeBankAccountEntity::getHolder)
.withProp(Fields.iban, HsOfficeBankAccountEntity::getIban)
.withProp(Fields.bic, HsOfficeBankAccountEntity::getBic); .withProp(Fields.bic, HsOfficeBankAccountEntity::getBic);
@Id @Id
@ -59,8 +59,11 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class) return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
.withIdentityView(SQL.projection("iban || ':' || holder")) .withIdentityView(SQL.projection("iban"))
.withUpdatableColumns("holder", "iban", "bic") .withUpdatableColumns("holder", "iban", "bic")
.toRole("global", GUEST).grantPermission(INSERT)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.owningUser(CREATOR); with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN); with.incomingSuperRole(GLOBAL, ADMIN);
@ -75,6 +78,6 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("243-hs-office-bankaccount-rbac-generated"); rbac().generateWithBaseFileName("243-hs-office-bankaccount-rbac");
} }
} }

View File

@ -75,10 +75,11 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
}) })
.createSubRole(REFERRER, (with) -> { .createSubRole(REFERRER, (with) -> {
with.permission(SELECT); with.permission(SELECT);
}); })
.toRole(GLOBAL, GUEST).grantPermission(INSERT);
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("203-hs-office-contact-rbac-generated"); rbac().generateWithBaseFileName("203-hs-office-contact-rbac");
} }
} }

View File

@ -12,6 +12,7 @@ import org.hibernate.annotations.GenericGenerator;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
@ -28,13 +29,12 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUuid { public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class) private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class)
.withProp(HsOfficeCoopAssetsTransactionEntity::getMemberNumber) .withIdProp(HsOfficeCoopAssetsTransactionEntity::getTaggedMemberNumber)
.withProp(HsOfficeCoopAssetsTransactionEntity::getValueDate) .withProp(HsOfficeCoopAssetsTransactionEntity::getValueDate)
.withProp(HsOfficeCoopAssetsTransactionEntity::getTransactionType) .withProp(HsOfficeCoopAssetsTransactionEntity::getTransactionType)
.withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue) .withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue)
.withProp(HsOfficeCoopAssetsTransactionEntity::getReference) .withProp(HsOfficeCoopAssetsTransactionEntity::getReference)
.withProp(HsOfficeCoopAssetsTransactionEntity::getComment) .withProp(HsOfficeCoopAssetsTransactionEntity::getComment)
.withSeparator(", ")
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -76,8 +76,8 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUu
private String comment; private String comment;
public Integer getMemberNumber() { public String getTaggedMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::getMemberNumber).orElse(null); return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-?????");
} }
@Override @Override
@ -87,6 +87,6 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUu
@Override @Override
public String toShortString() { public String toShortString() {
return "%s%+1.2f".formatted(getMemberNumber(), assetValue); return "%s:%+1.2f".formatted(getTaggedMemberNumber(), Optional.ofNullable(assetValue).orElse(BigDecimal.ZERO));
} }
} }

View File

@ -31,7 +31,6 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, HasUu
.withProp(HsOfficeCoopSharesTransactionEntity::getShareCount) .withProp(HsOfficeCoopSharesTransactionEntity::getShareCount)
.withProp(HsOfficeCoopSharesTransactionEntity::getReference) .withProp(HsOfficeCoopSharesTransactionEntity::getReference)
.withProp(HsOfficeCoopSharesTransactionEntity::getComment) .withProp(HsOfficeCoopSharesTransactionEntity::getComment)
.withSeparator(", ")
.quotedValues(false); .quotedValues(false);
@Id @Id

View File

@ -5,7 +5,11 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeDebitors
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
import org.apache.commons.lang3.Validate;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -13,10 +17,13 @@ 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.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
@RestController @RestController
public class HsOfficeDebitorController implements HsOfficeDebitorsApi { public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
@ -30,6 +37,9 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
@Autowired @Autowired
private HsOfficeDebitorRepository debitorRepo; private HsOfficeDebitorRepository debitorRepo;
@Autowired
private HsOfficeRelationRepository relRepo;
@PersistenceContext @PersistenceContext
private EntityManager em; private EntityManager em;
@ -53,22 +63,44 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeDebitorResource> addDebitor( public ResponseEntity<HsOfficeDebitorResource> addDebitor(
final String currentUser, String currentUser,
final String assumedRoles, String assumedRoles,
final HsOfficeDebitorInsertResource body) { HsOfficeDebitorInsertResource body) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class); Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRelUuid() == null,
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found both");
Validate.isTrue(body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found none");
Validate.isTrue(body.getDebitorRel() == null ||
body.getDebitorRel().getType() == null || DEBITOR.name().equals(body.getDebitorRel().getType()),
"ERROR: [400] debitorRel.type must be '"+DEBITOR.name()+"' or null for default");
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRel().getMark() == null,
"ERROR: [400] debitorRel.mark must be null");
final var saved = debitorRepo.save(entityToSave); final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
if ( body.getDebitorRel() != null ) {
body.getDebitorRel().setType(DEBITOR.name());
final var debitorRel = mapper.map(body.getDebitorRel(), HsOfficeRelationEntity.class);
entityToSave.setDebitorRel(relRepo.save(debitorRel));
} else {
final var debitorRelOptional = relRepo.findByUuid(body.getDebitorRelUuid());
debitorRelOptional.ifPresentOrElse(
debitorRel -> {entityToSave.setDebitorRel(relRepo.save(debitorRel));},
() -> { throw new EntityNotFoundException("ERROR: [400] debitorRelUuid not found: " + body.getDebitorRelUuid());});
}
final var savedEntity = debitorRepo.save(entityToSave);
em.flush();
em.refresh(savedEntity);
final var uri = final var uri =
MvcUriComponentsBuilder.fromController(getClass()) MvcUriComponentsBuilder.fromController(getClass())
.path("/api/hs/office/debitors/{id}") .path("/api/hs/office/debitors/{id}")
.buildAndExpand(saved.getUuid()) .buildAndExpand(savedEntity.getUuid())
.toUri(); .toUri();
final var mapped = mapper.map(saved, HsOfficeDebitorResource.class); final var mapped = mapper.map(savedEntity, HsOfficeDebitorResource.class);
return ResponseEntity.created(uri).body(mapped); return ResponseEntity.created(uri).body(mapped);
} }
@ -119,6 +151,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
new HsOfficeDebitorEntityPatcher(em, current).apply(body); new HsOfficeDebitorEntityPatcher(em, current).apply(body);
final var saved = debitorRepo.save(current); final var saved = debitorRepo.save(current);
Hibernate.initialize(saved);
final var mapped = mapper.map(saved, HsOfficeDebitorResource.class); final var mapped = mapper.map(saved, HsOfficeDebitorResource.class);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.debitor;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
@ -12,17 +11,26 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static jakarta.persistence.CascadeType.DETACH;
import static jakarta.persistence.CascadeType.MERGE;
import static jakarta.persistence.CascadeType.PERSIST;
import static jakarta.persistence.CascadeType.REFRESH;
import static java.util.Optional.ofNullable;
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.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
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.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@ -30,7 +38,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Table(name = "hs_office_debitor_rv") @Table(name = "hs_office_debitor_rv")
@Getter @Getter
@Setter @Setter
@Builder @Builder(toBuilder = true)
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("Debitor") @DisplayName("Debitor")
@ -38,15 +46,11 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
public static final String DEBITOR_NUMBER_TAG = "D-"; public static final String DEBITOR_NUMBER_TAG = "D-";
// TODO: I would rather like to generate something matching this example:
// debitor(1234500: Test AG, tes)
// maybe remove withSepararator (always use ', ') and add withBusinessIdProp (with ': ' afterwards)?
private static Stringify<HsOfficeDebitorEntity> stringify = private static Stringify<HsOfficeDebitorEntity> stringify =
stringify(HsOfficeDebitorEntity.class, "debitor") stringify(HsOfficeDebitorEntity.class, "debitor")
.withProp(e -> DEBITOR_NUMBER_TAG + e.getDebitorNumber()) .withIdProp(HsOfficeDebitorEntity::toShortString)
.withProp(HsOfficeDebitorEntity::getPartner) .withProp(e -> ofNullable(e.getDebitorRel()).map(HsOfficeRelationEntity::toShortString).orElse(null))
.withProp(HsOfficeDebitorEntity::getDefaultPrefix) .withProp(HsOfficeDebitorEntity::getDefaultPrefix)
.withSeparator(": ")
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -55,15 +59,28 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
private UUID uuid; private UUID uuid;
@ManyToOne @ManyToOne
@JoinColumn(name = "partneruuid") @JoinFormula(
referencedColumnName = "uuid",
value = """
(
SELECT DISTINCT partner.uuid
FROM hs_office_partner_rv partner
JOIN hs_office_relation_rv dRel
ON dRel.uuid = debitorreluuid AND dRel.type = 'DEBITOR'
JOIN hs_office_relation_rv pRel
ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER'
WHERE pRel.holderUuid = dRel.anchorUuid
)
""")
@NotFound(action = NotFoundAction.IGNORE)
private HsOfficePartnerEntity partner; private HsOfficePartnerEntity partner;
@Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)") @Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)")
private Byte debitorNumberSuffix; // TODO maybe rather as a formatted String? private Byte debitorNumberSuffix; // TODO maybe rather as a formatted String?
@ManyToOne @ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false)
@JoinColumn(name = "billingcontactuuid") @JoinColumn(name = "debitorreluuid", nullable = false)
private HsOfficeContactEntity billingContact; // TODO: migrate to billingPerson private HsOfficeRelationEntity debitorRel;
@Column(name = "billable", nullable = false) @Column(name = "billable", nullable = false)
private Boolean billable; // not a primitive because otherwise the default would be false private Boolean billable; // not a primitive because otherwise the default would be false
@ -88,14 +105,16 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
private String defaultPrefix; private String defaultPrefix;
private String getDebitorNumberString() { private String getDebitorNumberString() {
if (partner == null || partner.getPartnerNumber() == null || debitorNumberSuffix == null) { return ofNullable(partner)
return null; .filter(partner -> debitorNumberSuffix != null)
} .map(HsOfficePartnerEntity::getPartnerNumber)
return partner.getPartnerNumber() + String.format("%02d", debitorNumberSuffix); .map(Object::toString)
.map(partnerNumber -> partnerNumber + String.format("%02d", debitorNumberSuffix))
.orElse(null);
} }
public Integer getDebitorNumber() { public Integer getDebitorNumber() {
return Optional.ofNullable(getDebitorNumberString()).map(Integer::parseInt).orElse(null); return ofNullable(getDebitorNumberString()).map(Integer::parseInt).orElse(null);
} }
@Override @Override
@ -111,28 +130,28 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("debitor", HsOfficeDebitorEntity.class) return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
.withIdentityView(SQL.query(""" .withIdentityView(SQL.query("""
SELECT debitor.uuid, SELECT debitor.uuid AS uuid,
'D-' || (SELECT partner.partnerNumber 'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner FROM hs_office_partner partner
JOIN hs_office_relation partnerRel JOIN hs_office_relation partnerRel
ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER' ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER'
JOIN hs_office_relation debitorRel JOIN hs_office_relation debitorRel
ON debitorRel.anchorUuid = partnerRel.holderUuid AND partnerRel.type = 'DEBITOR' ON debitorRel.anchorUuid = partnerRel.holderUuid AND debitorRel.type = 'DEBITOR'
WHERE debitorRel.uuid = debitor.debitorRelUuid) WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00') || to_char(debitorNumberSuffix, 'fm00') as idName
from hs_office_debitor as debitor FROM hs_office_debitor AS debitor
""")) """))
.withRestrictedViewOrderBy(SQL.projection("defaultPrefix"))
.withUpdatableColumns( .withUpdatableColumns(
"debitorRel", "debitorRelUuid",
"billable", "billable",
"debitorUuid",
"refundBankAccountUuid", "refundBankAccountUuid",
"vatId", "vatId",
"vatCountryCode", "vatCountryCode",
"vatBusiness", "vatBusiness",
"vatReverseCharge", "vatReverseCharge",
"defaultPrefix" /* TODO: do we want that updatable? */) "defaultPrefix" /* TODO: do we want that updatable? */)
.createPermission(INSERT).grantedTo("global", ADMIN) .toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class,
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
@ -149,9 +168,16 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER) .toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
.importEntityAlias("partnerRel", HsOfficeRelationEntity.class, .importEntityAlias("partnerRel", HsOfficeRelationEntity.class,
dependsOnColumn("partnerRelUuid"), dependsOnColumn("debitorRelUuid"),
directlyFetchedByDependsOnColumn(), fetchedBySql("""
NULLABLE) SELECT ${columns}
FROM hs_office_relation AS partnerRel
JOIN hs_office_relation AS debitorRel
ON debitorRel.type = 'DEBITOR' AND debitorRel.anchorUuid = partnerRel.holderUuid
WHERE partnerRel.type = 'PARTNER'
AND ${REF}.debitorRelUuid = debitorRel.uuid
"""),
NOT_NULL)
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN) .toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT) .toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT) .toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)
@ -162,6 +188,6 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("273-hs-office-debitor-rbac-generated"); rbac().generateWithBaseFileName("273-hs-office-debitor-rbac");
} }
} }

View File

@ -1,8 +1,8 @@
package net.hostsharing.hsadminng.hs.office.debitor; package net.hostsharing.hsadminng.hs.office.debitor;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.OptionalFromJson;
@ -23,9 +23,9 @@ class HsOfficeDebitorEntityPatcher implements EntityPatcher<HsOfficeDebitorPatch
@Override @Override
public void apply(final HsOfficeDebitorPatchResource resource) { public void apply(final HsOfficeDebitorPatchResource resource) {
OptionalFromJson.of(resource.getBillingContactUuid()).ifPresent(newValue -> { OptionalFromJson.of(resource.getDebitorRelUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "billingContact"); verifyNotNull(newValue, "debitorRel");
entity.setBillingContact(em.getReference(HsOfficeContactEntity.class, newValue)); entity.setDebitorRel(em.getReference(HsOfficeRelationEntity.class, newValue));
}); });
Optional.ofNullable(resource.getBillable()).ifPresent(entity::setBillable); Optional.ofNullable(resource.getBillable()).ifPresent(entity::setBillable);
OptionalFromJson.of(resource.getVatId()).ifPresent(entity::setVatId); OptionalFromJson.of(resource.getVatId()).ifPresent(entity::setVatId);

View File

@ -13,7 +13,10 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query(""" @Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor SELECT debitor FROM HsOfficeDebitorEntity debitor
WHERE cast(debitor.partner.partnerNumber as integer) = :partnerNumber JOIN HsOfficePartnerEntity partner
ON partner.partnerRel.holder = debitor.debitorRel.anchor
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
WHERE cast(partner.partnerNumber as integer) = :partnerNumber
AND cast(debitor.debitorNumberSuffix as integer) = :debitorNumberSuffix AND cast(debitor.debitorNumberSuffix as integer) = :debitorNumberSuffix
""") """)
List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int partnerNumber, byte debitorNumberSuffix); List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int partnerNumber, byte debitorNumberSuffix);
@ -24,9 +27,15 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query(""" @Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor SELECT debitor FROM HsOfficeDebitorEntity debitor
JOIN HsOfficePartnerEntity partner ON partner.uuid = debitor.partner.uuid JOIN HsOfficePartnerEntity partner
JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid ON partner.partnerRel.holder = debitor.debitorRel.anchor
JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact.uuid AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
JOIN HsOfficePersonEntity person
ON person.uuid = partner.partnerRel.holder.uuid
OR person.uuid = debitor.debitorRel.holder.uuid
JOIN HsOfficeContactEntity contact
ON contact.uuid = debitor.debitorRel.contact.uuid
OR contact.uuid = partner.partnerRel.contact.uuid
WHERE :name is null WHERE :name is null
OR partner.details.birthName like concat(cast(:name as text), '%') OR partner.details.birthName like concat(cast(:name as text), '%')
OR person.tradeName like concat(cast(:name as text), '%') OR person.tradeName like concat(cast(:name as text), '%')

View File

@ -12,8 +12,6 @@ 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.PersistenceContext;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -32,9 +30,6 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Autowired @Autowired
private HsOfficeMembershipRepository membershipRepo; private HsOfficeMembershipRepository membershipRepo;
@PersistenceContext
private EntityManager em;
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeMembershipResource>> listMemberships( public ResponseEntity<List<HsOfficeMembershipResource>> listMemberships(
@ -121,7 +116,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
final var current = membershipRepo.findByUuid(membershipUuid).orElseThrow(); final var current = membershipRepo.findByUuid(membershipUuid).orElseThrow();
new HsOfficeMembershipEntityPatcher(em, mapper, current).apply(body); new HsOfficeMembershipEntityPatcher(mapper, current).apply(body);
final var saved = membershipRepo.save(current); final var saved = membershipRepo.save(current);
final var mapped = mapper.map(saved, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); final var mapped = mapper.map(saved, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER);

View File

@ -4,20 +4,30 @@ import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType;
import com.vladmihalcea.hibernate.type.range.Range; import com.vladmihalcea.hibernate.type.range.Range;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
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.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.REFERRER;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@ -35,10 +45,8 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class) private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class)
.withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber()) .withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber())
.withProp(e -> e.getPartner().toShortString()) .withProp(e -> e.getPartner().toShortString())
.withProp(e -> e.getMainDebitor().toShortString())
.withProp(e -> e.getValidity().asString()) .withProp(e -> e.getValidity().asString())
.withProp(HsOfficeMembershipEntity::getReasonForTermination) .withProp(HsOfficeMembershipEntity::getReasonForTermination)
.withSeparator(", ")
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -49,11 +57,6 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
@JoinColumn(name = "partneruuid") @JoinColumn(name = "partneruuid")
private HsOfficePartnerEntity partner; private HsOfficePartnerEntity partner;
@ManyToOne
@Fetch(FetchMode.JOIN)
@JoinColumn(name = "maindebitoruuid")
private HsOfficeDebitorEntity mainDebitor;
@Column(name = "membernumbersuffix", length = 2) @Column(name = "membernumbersuffix", length = 2)
private String memberNumberSuffix; private String memberNumberSuffix;
@ -114,4 +117,45 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
setReasonForTermination(HsOfficeReasonForTermination.NONE); setReasonForTermination(HsOfficeReasonForTermination.NONE);
} }
} }
public static RbacView rbac() {
return rbacViewFor("membership", HsOfficeMembershipEntity.class)
.withIdentityView(SQL.query("""
SELECT m.uuid AS uuid,
'M-' || p.partnerNumber || m.memberNumberSuffix as idName
FROM hs_office_membership AS m
JOIN hs_office_partner AS p ON p.uuid = m.partnerUuid
"""))
.withRestrictedViewOrderBy(SQL.projection("validity"))
.withUpdatableColumns("validity", "membershipFeeBillable", "reasonForTermination")
.importEntityAlias("partnerRel", HsOfficeRelationEntity.class,
dependsOnColumn("partnerUuid"),
fetchedBySql("""
SELECT ${columns}
FROM hs_office_partner AS partner
JOIN hs_office_relation AS partnerRel ON partnerRel.uuid = partner.partnerRelUuid
WHERE partner.uuid = ${REF}.partnerUuid
"""),
NOT_NULL)
.toRole("global", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole("partnerRel", ADMIN);
with.permission(DELETE);
})
.createSubRole(ADMIN, (with) -> {
with.incomingSuperRole("partnerRel", AGENT);
with.permission(UPDATE);
})
.createSubRole(REFERRER, (with) -> {
with.outgoingSubRole("partnerRel", TENANT);
with.permission(SELECT);
});
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("303-hs-office-membership-rbac");
}
} }

View File

@ -1,37 +1,26 @@
package net.hostsharing.hsadminng.hs.office.membership; package net.hostsharing.hsadminng.hs.office.membership;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import jakarta.persistence.EntityManager;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> { public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
private final EntityManager em;
private final Mapper mapper; private final Mapper mapper;
private final HsOfficeMembershipEntity entity; private final HsOfficeMembershipEntity entity;
public HsOfficeMembershipEntityPatcher( public HsOfficeMembershipEntityPatcher(
final EntityManager em,
final Mapper mapper, final Mapper mapper,
final HsOfficeMembershipEntity entity) { final HsOfficeMembershipEntity entity) {
this.em = em;
this.mapper = mapper; this.mapper = mapper;
this.entity = entity; this.entity = entity;
} }
@Override @Override
public void apply(final HsOfficeMembershipPatchResource resource) { public void apply(final HsOfficeMembershipPatchResource resource) {
OptionalFromJson.of(resource.getMainDebitorUuid())
.ifPresent(newValue -> {
verifyNotNull(newValue, "debitor");
entity.setMainDebitor(em.getReference(HsOfficeDebitorEntity.class, newValue));
});
OptionalFromJson.of(resource.getValidTo()).ifPresent( OptionalFromJson.of(resource.getValidTo()).ifPresent(
entity::setValidTo); entity::setValidTo);
Optional.ofNullable(resource.getReasonForTermination()) Optional.ofNullable(resource.getReasonForTermination())
@ -40,10 +29,4 @@ public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMe
OptionalFromJson.of(resource.getMembershipFeeBillable()).ifPresent( OptionalFromJson.of(resource.getMembershipFeeBillable()).ifPresent(
entity::setMembershipFeeBillable); entity::setMembershipFeeBillable);
} }
private void verifyNotNull(final UUID newValue, final String propertyName) {
if (newValue == null) {
throw new IllegalArgumentException("property '" + propertyName + "' must not be null");
}
}
} }

View File

@ -110,9 +110,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
if (partnerRepo.deleteByUuid(partnerUuid) != 1 || if (partnerRepo.deleteByUuid(partnerUuid) != 1) {
// TODO: move to after delete trigger in partner
relationRepo.deleteByUuid(partnerToDelete.get().getPartnerRel().getUuid()) != 1 ) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
} }
@ -142,8 +140,6 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final var entityToSave = new HsOfficePartnerEntity(); final var entityToSave = new HsOfficePartnerEntity();
entityToSave.setPartnerNumber(body.getPartnerNumber()); entityToSave.setPartnerNumber(body.getPartnerNumber());
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel())); entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
entityToSave.setContact(ref(HsOfficeContactEntity.class, body.getContactUuid()));
entityToSave.setPerson(ref(HsOfficePersonEntity.class, body.getPersonUuid()));
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class)); entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
return entityToSave; return entityToSave;
} }

View File

@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
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;
@ -14,10 +13,8 @@ import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@ -40,7 +37,6 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
.withProp(HsOfficePartnerDetailsEntity::getBirthday) .withProp(HsOfficePartnerDetailsEntity::getBirthday)
.withProp(HsOfficePartnerDetailsEntity::getBirthName) .withProp(HsOfficePartnerDetailsEntity::getBirthName)
.withProp(HsOfficePartnerDetailsEntity::getDateOfDeath) .withProp(HsOfficePartnerDetailsEntity::getDateOfDeath)
.withSeparator(", ")
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -72,11 +68,12 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class) return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
.withIdentityView(SQL.query(""" .withIdentityView(SQL.query("""
SELECT partner_iv.idName || '-details' SELECT partnerDetails.uuid as uuid, partner_iv.idName || '-details' as idName
FROM hs_office_partner_details AS partnerDetails FROM hs_office_partner_details AS partnerDetails
JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid
JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid
""")) """))
.withRestrictedViewOrderBy(SQL.expression("uuid"))
.withUpdatableColumns( .withUpdatableColumns(
"registrationOffice", "registrationOffice",
"registrationNumber", "registrationNumber",
@ -84,17 +81,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
"birthName", "birthName",
"birthday", "birthday",
"dateOfDeath") "dateOfDeath")
.createPermission(INSERT).grantedTo("global", ADMIN) .toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
fetchedBySql("""
SELECT ${columns}
FROM hs_office_relation AS partnerRel
JOIN hs_office_partner AS partner
ON partner.detailsUuid = ${ref}.uuid
WHERE partnerRel.uuid = partner.partnerRelUuid
"""),
dependsOnColumn("partnerRelUuid"))
// The grants are defined in HsOfficePartnerEntity.rbac() // The grants are defined in HsOfficePartnerEntity.rbac()
// because they have to be changed when its partnerRel changes, // because they have to be changed when its partnerRel changes,
@ -103,6 +90,6 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("234-hs-office-partner-details-rbac-generated"); rbac().generateWithBaseFileName("234-hs-office-partner-details-rbac");
} }
} }

View File

@ -1,10 +1,14 @@
package net.hostsharing.hsadminng.hs.office.partner; package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
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;
@ -13,17 +17,24 @@ import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.*; import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static jakarta.persistence.CascadeType.*;
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.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
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.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
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;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@ -36,10 +47,18 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@DisplayName("Partner") @DisplayName("Partner")
public class HsOfficePartnerEntity implements Stringifyable, HasUuid { public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
public static final String PARTNER_NUMBER_TAG = "P-";
private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner") private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner")
.withProp(HsOfficePartnerEntity::getPerson) .withIdProp(HsOfficePartnerEntity::toShortString)
.withProp(HsOfficePartnerEntity::getContact) .withProp(p -> ofNullable(p.getPartnerRel())
.withSeparator(": ") .map(HsOfficeRelationEntity::getHolder)
.map(HsOfficePersonEntity::toShortString)
.orElse(null))
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelationEntity::getContact)
.map(HsOfficeContactEntity::toShortString)
.orElse(null))
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -49,25 +68,19 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null") @Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
private Integer partnerNumber; private Integer partnerNumber;
@ManyToOne @ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false)
@JoinColumn(name = "partnerreluuid", nullable = false) @JoinColumn(name = "partnerreluuid", nullable = false)
private HsOfficeRelationEntity partnerRel; private HsOfficeRelationEntity partnerRel;
// TODO: remove, is replaced by partnerRel @ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true)
@ManyToOne
@JoinColumn(name = "personuuid", nullable = false)
private HsOfficePersonEntity person;
// TODO: remove, is replaced by partnerRel
@ManyToOne
@JoinColumn(name = "contactuuid", nullable = false)
private HsOfficeContactEntity contact;
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH }, optional = true)
@JoinColumn(name = "detailsuuid") @JoinColumn(name = "detailsuuid")
@NotFound(action = NotFoundAction.IGNORE) @NotFound(action = NotFoundAction.IGNORE)
private HsOfficePartnerDetailsEntity details; private HsOfficePartnerDetailsEntity details;
public String getTaggedPartnerNumber() {
return PARTNER_NUMBER_TAG + partnerNumber;
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);
@ -75,22 +88,14 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
@Override @Override
public String toShortString() { public String toShortString() {
return Optional.ofNullable(person).map(HsOfficePersonEntity::toShortString).orElse("<person=null>"); return getTaggedPartnerNumber();
} }
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("partner", HsOfficePartnerEntity.class) return rbacViewFor("partner", HsOfficePartnerEntity.class)
.withIdentityView(SQL.query(""" .withIdentityView(SQL.projection("'P-' || partnerNumber"))
SELECT partner.partnerNumber .withUpdatableColumns("partnerRelUuid")
|| ':' || (SELECT idName FROM hs_office_person_iv p WHERE p.uuid = partner.personUuid) .toRole("global", ADMIN).grantPermission(INSERT)
|| '-' || (SELECT idName FROM hs_office_contact_iv c WHERE c.uuid = partner.contactUuid)
FROM hs_office_partner AS partner
"""))
.withUpdatableColumns(
"partnerRelUuid",
"personUuid",
"contactUuid")
.createPermission(INSERT).grantedTo("global", ADMIN)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
@ -108,6 +113,6 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("233-hs-office-partner-rbac-generated"); rbac().generateWithBaseFileName("233-hs-office-partner-rbac");
} }
} }

View File

@ -1,13 +1,11 @@
package net.hostsharing.hsadminng.hs.office.partner; package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import java.util.UUID;
class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> { class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> {
private final EntityManager em; private final EntityManager em;
@ -21,19 +19,15 @@ class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatch
@Override @Override
public void apply(final HsOfficePartnerPatchResource resource) { public void apply(final HsOfficePartnerPatchResource resource) {
OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> { OptionalFromJson.of(resource.getPartnerRelUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "contact"); verifyNotNull(newValue, "partnerRel");
entity.setContact(em.getReference(HsOfficeContactEntity.class, newValue)); entity.setPartnerRel(em.getReference(HsOfficeRelationEntity.class, newValue));
});
OptionalFromJson.of(resource.getPersonUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "person");
entity.setPerson(em.getReference(HsOfficePersonEntity.class, newValue));
}); });
new HsOfficePartnerDetailsEntityPatcher(em, entity.getDetails()).apply(resource.getDetails()); new HsOfficePartnerDetailsEntityPatcher(em, entity.getDetails()).apply(resource.getDetails());
} }
private void verifyNotNull(final UUID newValue, final String propertyName) { private void verifyNotNull(final Object newValue, final String propertyName) {
if (newValue == null) { if (newValue == null) {
throw new IllegalArgumentException("property '" + propertyName + "' must not be null"); throw new IllegalArgumentException("property '" + propertyName + "' must not be null");
} }

View File

@ -11,10 +11,13 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
Optional<HsOfficePartnerEntity> findByUuid(UUID id); Optional<HsOfficePartnerEntity> findByUuid(UUID id);
List<HsOfficePartnerEntity> findAll(); // TODO: move to a repo in test sources
@Query(""" @Query("""
SELECT partner FROM HsOfficePartnerEntity partner SELECT partner FROM HsOfficePartnerEntity partner
JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact.uuid JOIN HsOfficeRelationEntity rel ON rel.uuid = partner.partnerRel.uuid
JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid JOIN HsOfficeContactEntity contact ON contact.uuid = rel.contact.uuid
JOIN HsOfficePersonEntity person ON person.uuid = rel.holder.uuid
WHERE :name is null WHERE :name is null
OR partner.details.birthName like concat(cast(:name as text), '%') OR partner.details.birthName like concat(cast(:name as text), '%')
OR contact.label like concat(cast(:name as text), '%') OR contact.label like concat(cast(:name as text), '%')

View File

@ -69,6 +69,8 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
return rbacViewFor("person", HsOfficePersonEntity.class) return rbacViewFor("person", HsOfficePersonEntity.class)
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)")) .withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName") .withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
.toRole("global", GUEST).grantPermission(INSERT)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.permission(DELETE); with.permission(DELETE);
with.owningUser(CREATOR); with.owningUser(CREATOR);
@ -84,6 +86,6 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("213-hs-office-person-rbac-generated"); rbac().generateWithBaseFileName("213-hs-office-person-rbac");
} }
} }

View File

@ -16,7 +16,7 @@ import java.util.UUID;
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.GLOBAL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -92,22 +92,26 @@ public class HsOfficeRelationEntity implements HasUuid, Stringifyable {
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class, .importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
dependsOnColumn("anchorUuid"), dependsOnColumn("anchorUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NULLABLE) NOT_NULL)
.importEntityAlias("holderPerson", HsOfficePersonEntity.class, .importEntityAlias("holderPerson", HsOfficePersonEntity.class,
dependsOnColumn("holderUuid"), dependsOnColumn("holderUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NULLABLE) NOT_NULL)
.importEntityAlias("contact", HsOfficeContactEntity.class, .importEntityAlias("contact", HsOfficeContactEntity.class,
dependsOnColumn("contactUuid"), dependsOnColumn("contactUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NULLABLE) NOT_NULL)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.owningUser(CREATOR); with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN); with.incomingSuperRole(GLOBAL, ADMIN);
// TODO: if type=REPRESENTATIIVE
// with.incomingSuperRole("holderPerson", ADMIN);
with.permission(DELETE); with.permission(DELETE);
}) })
.createSubRole(ADMIN, (with) -> { .createSubRole(ADMIN, (with) -> {
with.incomingSuperRole("anchorPerson", ADMIN); with.incomingSuperRole("anchorPerson", ADMIN);
// TODO: if type=REPRESENTATIIVE
// with.outgoingSuperRole("anchorPerson", OWNER);
with.permission(UPDATE); with.permission(UPDATE);
}) })
.createSubRole(AGENT, (with) -> { .createSubRole(AGENT, (with) -> {
@ -120,10 +124,12 @@ public class HsOfficeRelationEntity implements HasUuid, Stringifyable {
with.outgoingSubRole("holderPerson", REFERRER); with.outgoingSubRole("holderPerson", REFERRER);
with.outgoingSubRole("contact", REFERRER); with.outgoingSubRole("contact", REFERRER);
with.permission(SELECT); with.permission(SELECT);
}); })
.toRole("anchorPerson", ADMIN).grantPermission(INSERT);
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("223-hs-office-relation-rbac-generated"); rbac().generateWithBaseFileName("223-hs-office-relation-rbac");
} }
} }

View File

@ -132,6 +132,7 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
if (entity.getValidity().hasUpperBound()) { if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1)); resource.setValidTo(entity.getValidity().upper().minusDays(1));
} }
resource.getDebitor().setDebitorNumber(entity.getDebitor().getDebitorNumber());
}; };
final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {

View File

@ -21,6 +21,7 @@ import java.util.UUID;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
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.GLOBAL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -43,7 +44,6 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
.withProp(HsOfficeSepaMandateEntity::getReference) .withProp(HsOfficeSepaMandateEntity::getReference)
.withProp(HsOfficeSepaMandateEntity::getAgreement) .withProp(HsOfficeSepaMandateEntity::getAgreement)
.withProp(e -> e.getValidity().asString()) .withProp(e -> e.getValidity().asString())
.withSeparator(", ")
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -96,11 +96,27 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
public static RbacView rbac() { public static RbacView rbac() {
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class) return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
.withIdentityView(projection("concat(tradeName, familyName, givenName)")) .withIdentityView(query("""
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
from hs_office_sepamandate sm
join hs_office_bankaccount ba on ba.uuid = sm.bankAccountUuid
"""))
.withRestrictedViewOrderBy(expression("validity"))
.withUpdatableColumns("reference", "agreement", "validity") .withUpdatableColumns("reference", "agreement", "validity")
.importEntityAlias("debitorRel", HsOfficeRelationEntity.class, dependsOnColumn("debitorRelUuid")) .importEntityAlias("debitorRel", HsOfficeRelationEntity.class,
.importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, dependsOnColumn("bankAccountUuid")) dependsOnColumn("debitorUuid"),
fetchedBySql("""
SELECT ${columns}
FROM hs_office_relation debitorRel
JOIN hs_office_debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
WHERE debitor.uuid = ${REF}.debitorUuid
"""),
NOT_NULL)
.importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class,
dependsOnColumn("bankAccountUuid"),
directlyFetchedByDependsOnColumn(),
NOT_NULL)
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.owningUser(CREATOR); with.owningUser(CREATOR);
@ -119,10 +135,12 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
with.incomingSuperRole("debitorRel", AGENT); with.incomingSuperRole("debitorRel", AGENT);
with.outgoingSubRole("debitorRel", TENANT); with.outgoingSubRole("debitorRel", TENANT);
with.permission(SELECT); with.permission(SELECT);
}); })
.toRole("debitorRel", ADMIN).grantPermission(INSERT);
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("253-hs-office-sepamandate-rbac-generated"); rbac().generateWithBaseFileName("253-hs-office-sepamandate-rbac");
} }
} }

View File

@ -47,16 +47,14 @@ public class InsertTriggerGenerator {
do language plpgsql $$ do language plpgsql $$
declare declare
row ${rawSuperTableName}; row ${rawSuperTableName};
permissionUuid uuid;
roleUuid uuid;
begin begin
call defineContext('create INSERT INTO ${rawSubTableName} permissions for the related ${rawSuperTableName} rows'); call defineContext('create INSERT INTO ${rawSubTableName} permissions for the related ${rawSuperTableName} rows');
FOR row IN SELECT * FROM ${rawSuperTableName} FOR row IN SELECT * FROM ${rawSuperTableName}
LOOP LOOP
roleUuid := findRoleId(${rawSuperRoleDescriptor}); call grantPermissionToRole(
permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}'); createPermission(row.uuid, 'INSERT', '${rawSubTableName}'),
call grantPermissionToRole(permissionUuid, roleUuid); ${rawSuperRoleDescriptor});
END LOOP; END LOOP;
END; END;
$$; $$;

View File

@ -16,6 +16,7 @@ public final class Stringify<B> {
private final Class<B> clazz; private final Class<B> clazz;
private final String name; private final String name;
private Function<B, ?> idProp;
private final List<Property<B>> props = new ArrayList<>(); private final List<Property<B>> props = new ArrayList<>();
private String separator = ", "; private String separator = ", ";
private Boolean quotedValues = null; private Boolean quotedValues = null;
@ -42,6 +43,11 @@ public final class Stringify<B> {
} }
} }
public Stringify<B> withIdProp(final Function<B, ?> getter) {
idProp = getter;
return this;
}
public Stringify<B> withProp(final String propName, final Function<B, ?> getter) { public Stringify<B> withProp(final String propName, final Function<B, ?> getter) {
props.add(new Property<>(propName, getter)); props.add(new Property<>(propName, getter));
return this; return this;
@ -64,7 +70,9 @@ public final class Stringify<B> {
}) })
.map(propVal -> propName(propVal, "=") + optionallyQuoted(propVal)) .map(propVal -> propName(propVal, "=") + optionallyQuoted(propVal))
.collect(Collectors.joining(separator)); .collect(Collectors.joining(separator));
return name + "(" + propValues + ")"; return idProp != null
? name + "(" + idProp.apply(object) + ": " + propValues + ")"
: name + "(" + propValues + ")";
} }
public Stringify<B> withSeparator(final String separator) { public Stringify<B> withSeparator(final String separator) {

View File

@ -9,6 +9,8 @@ components:
uuid: uuid:
type: string type: string
format: uuid format: uuid
debitorRel:
$ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
debitorNumber: debitorNumber:
type: integer type: integer
format: int32 format: int32
@ -21,8 +23,6 @@ components:
maximum: 99 maximum: 99
partner: partner:
$ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner' $ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner'
billingContact:
$ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact'
billable: billable:
type: boolean type: boolean
vatId: vatId:
@ -43,7 +43,7 @@ components:
HsOfficeDebitorPatch: HsOfficeDebitorPatch:
type: object type: object
properties: properties:
billingContactUuid: debitorRelUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -75,14 +75,11 @@ components:
HsOfficeDebitorInsert: HsOfficeDebitorInsert:
type: object type: object
properties: properties:
partnerUuid: debitorRel:
$ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationInsert'
debitorRelUuid:
type: string type: string
format: uuid format: uuid
nullable: false
billingContactUuid:
type: string
format: uuid
nullable: false
debitorNumberSuffix: debitorNumberSuffix:
type: integer type: integer
format: int8 format: int8
@ -105,9 +102,7 @@ components:
defaultPrefix: defaultPrefix:
type: string type: string
pattern: '^[a-z]{3}$' pattern: '^[a-z]{3}$'
required: required:
- partnerUuid - debitorNumberSuffix
- billingContactUuid
- defaultPrefix - defaultPrefix
- billable - billable

View File

@ -46,10 +46,6 @@ components:
HsOfficeMembershipPatch: HsOfficeMembershipPatch:
type: object type: object
properties: properties:
mainDebitorUuid:
type: string
format: uuid
nullable: true
validTo: validTo:
type: string type: string
format: date format: date
@ -69,10 +65,6 @@ components:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
mainDebitorUuid:
type: string
format: uuid
nullable: false
memberNumberSuffix: memberNumberSuffix:
type: string type: string
minLength: 2 minLength: 2
@ -95,7 +87,6 @@ components:
required: required:
- partnerUuid - partnerUuid
- memberNumberSuffix - memberNumberSuffix
- mainDebitorUuid
- validFrom - validFrom
- membershipFeeBillable - membershipFeeBillable
additionalProperties: false additionalProperties: false

View File

@ -14,10 +14,8 @@ components:
format: int8 format: int8
minimum: 10000 minimum: 10000
maximum: 99999 maximum: 99999
person: partnerRel:
$ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson' $ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
contact:
$ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact'
details: details:
$ref: '#/components/schemas/HsOfficePartnerDetails' $ref: '#/components/schemas/HsOfficePartnerDetails'
@ -52,11 +50,7 @@ components:
HsOfficePartnerPatch: HsOfficePartnerPatch:
type: object type: object
properties: properties:
personUuid: partnerRelUuid:
type: string
format: uuid
nullable: true
contactUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -98,18 +92,11 @@ components:
maximum: 99999 maximum: 99999
partnerRel: partnerRel:
$ref: '#/components/schemas/HsOfficePartnerRelInsert' $ref: '#/components/schemas/HsOfficePartnerRelInsert'
personUuid:
type: string
format: uuid
contactUuid:
type: string
format: uuid
details: details:
$ref: '#/components/schemas/HsOfficePartnerDetailsInsert' $ref: '#/components/schemas/HsOfficePartnerDetailsInsert'
required: required:
- partnerNumber - partnerNumber
- personUuid - partnerRel
- contactUuid
- details - details
HsOfficePartnerRelInsert: HsOfficePartnerRelInsert:

View File

@ -19,7 +19,7 @@ get:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation' $ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: './error-responses.yaml#/components/responses/Unauthorized'
@ -44,14 +44,14 @@ patch:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationPatch' $ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationPatch'
responses: responses:
"200": "200":
description: OK description: OK
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation' $ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: './error-responses.yaml#/components/responses/Unauthorized'
"403": "403":

View File

@ -18,7 +18,7 @@ get:
in: query in: query
required: false required: false
schema: schema:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationType' $ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationType'
description: Prefix of name properties from holder or contact to filter the results. description: Prefix of name properties from holder or contact to filter the results.
responses: responses:
"200": "200":
@ -28,7 +28,7 @@ get:
schema: schema:
type: array type: array
items: items:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation' $ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: './error-responses.yaml#/components/responses/Unauthorized'
"403": "403":
@ -46,7 +46,7 @@ post:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationInsert' $ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationInsert'
required: true required: true
responses: responses:
"201": "201":
@ -54,7 +54,7 @@ post:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation' $ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: './error-responses.yaml#/components/responses/Unauthorized'
"403": "403":

View File

@ -22,5 +22,6 @@ components:
- owner - owner
- admin - admin
- tenant - tenant
- referrer
roleName: roleName:
type: string type: string

View File

@ -3,7 +3,7 @@
-- ============================================================================ -- ============================================================================
-- NUMERIC-HASH-FUNCTIONS -- NUMERIC-HASH-FUNCTIONS
--changeset hash:1 endDelimiter:--// --changeset numeric-hash-functions:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
create function bigIntHash(text) returns bigint as $$ create function bigIntHash(text) returns bigint as $$

View File

@ -26,7 +26,7 @@ create or replace procedure defineContext(
currentTask varchar(96), currentTask varchar(96),
currentRequest text = null, currentRequest text = null,
currentUser varchar(63) = null, currentUser varchar(63) = null,
assumedRoles varchar(256) = null assumedRoles varchar(1023) = null
) )
language plpgsql as $$ language plpgsql as $$
begin begin
@ -43,7 +43,7 @@ begin
execute format('set local hsadminng.currentUser to %L', currentUser); execute format('set local hsadminng.currentUser to %L', currentUser);
assumedRoles := coalesce(assumedRoles, ''); assumedRoles := coalesce(assumedRoles, '');
assert length(assumedRoles) <= 256, FORMAT('assumedRoles must not be longer than 256 characters: "%s"', assumedRoles); assert length(assumedRoles) <= 1023, FORMAT('assumedRoles must not be longer than 1023 characters: "%s"', assumedRoles);
execute format('set local hsadminng.assumedRoles to %L', assumedRoles); execute format('set local hsadminng.assumedRoles to %L', assumedRoles);
call contextDefined(currentTask, currentRequest, currentUser, assumedRoles); call contextDefined(currentTask, currentRequest, currentUser, assumedRoles);
@ -87,11 +87,11 @@ end; $$;
Raises exception if not set. Raises exception if not set.
*/ */
create or replace function currentRequest() create or replace function currentRequest()
returns varchar(512) returns text
stable -- leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentRequest varchar(512); currentRequest text;
begin begin
begin begin
currentRequest := current_setting('hsadminng.currentRequest'); currentRequest := current_setting('hsadminng.currentRequest');
@ -135,22 +135,11 @@ end; $$;
or empty array, if not set. or empty array, if not set.
*/ */
create or replace function assumedRoles() create or replace function assumedRoles()
returns varchar(63)[] returns varchar(1023)[]
stable -- leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare
currentSubject varchar(63);
begin begin
begin return string_to_array(current_setting('hsadminng.assumedRoles', true), ';');
currentSubject := current_setting('hsadminng.assumedRoles');
exception
when others then
return array []::varchar[];
end;
if (currentSubject = '') then
return array []::varchar[];
end if;
return string_to_array(currentSubject, ';');
end; $$; end; $$;
create or replace function cleanIdentifier(rawIdentifier varchar) create or replace function cleanIdentifier(rawIdentifier varchar)
@ -219,17 +208,17 @@ begin
end ; $$; end ; $$;
create or replace function currentSubjects() create or replace function currentSubjects()
returns varchar(63)[] returns varchar(1023)[]
stable -- leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
assumedRoles varchar(63)[]; assumedRoles varchar(1023)[];
begin begin
assumedRoles := assumedRoles(); assumedRoles := assumedRoles();
if array_length(assumedRoles, 1) > 0 then if array_length(assumedRoles, 1) > 0 then
return assumedRoles(); return assumedRoles;
else else
return array [currentUser()]::varchar(63)[]; return array [currentUser()]::varchar(1023)[];
end if; end if;
end; $$; end; $$;

View File

@ -27,9 +27,9 @@ create table tx_context
txId bigint not null, txId bigint not null,
txTimestamp timestamp not null, txTimestamp timestamp not null,
currentUser varchar(63) not null, -- not the uuid, because users can be deleted currentUser varchar(63) not null, -- not the uuid, because users can be deleted
assumedRoles varchar(256) not null, -- not the uuids, because roles can be deleted assumedRoles varchar(1023) not null, -- not the uuids, because roles can be deleted
currentTask varchar(96) not null, currentTask varchar(96) not null,
currentRequest text not null currentRequest text not null
); );
create index on tx_context using brin (txTimestamp); create index on tx_context using brin (txTimestamp);

View File

@ -63,6 +63,7 @@ create or replace view rbacgrants_ev as
x.grantedByRoleUuid, x.grantedByRoleUuid,
x.ascendantUuid as ascendantUuid, x.ascendantUuid as ascendantUuid,
x.descendantUuid as descendantUuid, x.descendantUuid as descendantUuid,
x.op as permOp, x.optablename as permOpTableName,
x.assumed x.assumed
from ( from (
select g.uuid as grantUuid, select g.uuid as grantUuid,
@ -74,13 +75,17 @@ create or replace view rbacgrants_ev as
'role ' || aro.objectTable || '#' || findIdNameByObjectUuid(aro.objectTable, aro.uuid) || '.' || ar.roletype 'role ' || aro.objectTable || '#' || findIdNameByObjectUuid(aro.objectTable, aro.uuid) || '.' || ar.roletype
) as ascendingIdName, ) as ascendingIdName,
aro.objectTable, aro.uuid, aro.objectTable, aro.uuid,
( case
coalesce( when dro is not null
'role ' || dro.objectTable || '#' || findIdNameByObjectUuid(dro.objectTable, dro.uuid) || '.' || dr.roletype, then ('role ' || dro.objectTable || '#' || findIdNameByObjectUuid(dro.objectTable, dro.uuid) || '.' || dr.roletype)
'perm ' || dp.op || ' on ' || dpo.objecttable || '#' || findIdNameByObjectUuid(dpo.objectTable, dpo.uuid) when dp.op = 'INSERT'
then 'perm ' || dp.op || ' into ' || dp.opTableName || ' with ' || dpo.objecttable || '#' || findIdNameByObjectUuid(dpo.objectTable, dpo.uuid)
else 'perm ' || dp.op || ' on ' || dpo.objecttable || '#' || findIdNameByObjectUuid(dpo.objectTable, dpo.uuid)
end
) as descendingIdName, ) as descendingIdName,
dro.objectTable, dro.uuid dro.objectTable, dro.uuid,
from rbacgrants as g dp.op, dp.optablename
from rbacgrants as g
left outer join rbacrole as ar on ar.uuid = g.ascendantUuid left outer join rbacrole as ar on ar.uuid = g.ascendantUuid
left outer join rbacobject as aro on aro.uuid = ar.objectuuid left outer join rbacobject as aro on aro.uuid = ar.objectuuid

View File

@ -86,16 +86,14 @@ execute procedure insertTriggerForTestCustomer_tf();
do language plpgsql $$ do language plpgsql $$
declare declare
row global; row global;
permissionUuid uuid;
roleUuid uuid;
begin begin
call defineContext('create INSERT INTO test_customer permissions for the related global rows'); call defineContext('create INSERT INTO test_customer permissions for the related global rows');
FOR row IN SELECT * FROM global FOR row IN SELECT * FROM global
LOOP LOOP
roleUuid := findRoleId(globalAdmin()); call grantPermissionToRole(
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_customer'); createPermission(row.uuid, 'INSERT', 'test_customer'),
call grantPermissionToRole(permissionUuid, roleUuid); globalAdmin());
END LOOP; END LOOP;
END; END;
$$; $$;

View File

@ -151,16 +151,14 @@ execute procedure updateTriggerForTestPackage_tf();
do language plpgsql $$ do language plpgsql $$
declare declare
row test_customer; row test_customer;
permissionUuid uuid;
roleUuid uuid;
begin begin
call defineContext('create INSERT INTO test_package permissions for the related test_customer rows'); call defineContext('create INSERT INTO test_package permissions for the related test_customer rows');
FOR row IN SELECT * FROM test_customer FOR row IN SELECT * FROM test_customer
LOOP LOOP
roleUuid := findRoleId(testCustomerAdmin(row)); call grantPermissionToRole(
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_package'); createPermission(row.uuid, 'INSERT', 'test_package'),
call grantPermissionToRole(permissionUuid, roleUuid); testCustomerAdmin(row));
END LOOP; END LOOP;
END; END;
$$; $$;

View File

@ -150,16 +150,14 @@ execute procedure updateTriggerForTestDomain_tf();
do language plpgsql $$ do language plpgsql $$
declare declare
row test_package; row test_package;
permissionUuid uuid;
roleUuid uuid;
begin begin
call defineContext('create INSERT INTO test_domain permissions for the related test_package rows'); call defineContext('create INSERT INTO test_domain permissions for the related test_package rows');
FOR row IN SELECT * FROM test_package FOR row IN SELECT * FROM test_package
LOOP LOOP
roleUuid := findRoleId(testPackageAdmin(row)); call grantPermissionToRole(
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_domain'); createPermission(row.uuid, 'INSERT', 'test_domain'),
call grantPermissionToRole(permissionUuid, roleUuid); testPackageAdmin(row));
END LOOP; END LOOP;
END; END;
$$; $$;

View File

@ -1,126 +0,0 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-office-contact-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_contact');
--//
-- ============================================================================
--changeset hs-office-contact-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficeContact', 'hs_office_contact');
--//
-- ============================================================================
--changeset hs-office-contact-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficeContact(
NEW hs_office_contact
)
language plpgsql as $$
declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
perform createRoleWithGrants(
hsOfficeContactOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficeContactAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeContactOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeContactReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeContactAdmin(NEW)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_contact row.
*/
create or replace function insertTriggerForHsOfficeContact_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeContact(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeContact_tg
after insert on hs_office_contact
for each row
execute procedure insertTriggerForHsOfficeContact_tf();
--//
-- ============================================================================
--changeset hs-office-contact-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_contact,
where only global-admin has that permission.
*/
create or replace function hs_office_contact_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_contact not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_contact_insert_permission_check_tg
before insert on hs_office_contact
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_contact_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-contact-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_contact',
$idName$
label
$idName$);
--//
-- ============================================================================
--changeset hs-office-contact-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_contact',
$orderBy$
label
$orderBy$,
$updates$
label = new.label,
postalAddress = new.postalAddress,
emailAddresses = new.emailAddresses,
phoneNumbers = new.phoneNumbers
$updates$);
--//

View File

@ -24,6 +24,7 @@ subgraph contact["`**contact**`"]
perm:contact:DELETE{{contact:DELETE}} perm:contact:DELETE{{contact:DELETE}}
perm:contact:UPDATE{{contact:UPDATE}} perm:contact:UPDATE{{contact:UPDATE}}
perm:contact:SELECT{{contact:SELECT}} perm:contact:SELECT{{contact:SELECT}}
perm:contact:INSERT{{contact:INSERT}}
end end
end end
@ -39,5 +40,6 @@ role:contact:admin ==> role:contact:referrer
role:contact:owner ==> perm:contact:DELETE role:contact:owner ==> perm:contact:DELETE
role:contact:admin ==> perm:contact:UPDATE role:contact:admin ==> perm:contact:UPDATE
role:contact:referrer ==> perm:contact:SELECT role:contact:referrer ==> perm:contact:SELECT
role:global:guest ==> perm:contact:INSERT
``` ```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-contact-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-contact-rbac-OBJECT:1 endDelimiter:--//
@ -15,127 +17,130 @@ call generateRbacRoleDescriptors('hsOfficeContact', 'hs_office_contact');
-- ============================================================================ -- ============================================================================
--changeset hs-office-contact-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-contact-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates the roles and their assignments for a new contact for the AFTER INSERT TRIGGER. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function createRbacRolesForHsOfficeContact() create or replace procedure buildRbacSystemForHsOfficeContact(
NEW hs_office_contact
)
language plpgsql as $$
declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
perform createRoleWithGrants(
hsOfficeContactOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficeContactAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeContactOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeContactReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeContactAdmin(NEW)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_contact row.
*/
create or replace function insertTriggerForHsOfficeContact_tf()
returns trigger returns trigger
language plpgsql language plpgsql
strict as $$ strict as $$
begin begin
if TG_OP <> 'INSERT' then call buildRbacSystemForHsOfficeContact(NEW);
raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if;
perform createRoleWithGrants(
hsOfficeContactOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()],
grantedByRole => globalAdmin()
);
perform createRoleWithGrants(
hsOfficeContactAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeContactOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeContactTenant(NEW),
incomingSuperRoles => array[hsOfficeContactAdmin(NEW)]
);
perform createRoleWithGrants(
hsOfficeContactGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeContactTenant(NEW)]
);
return NEW; return NEW;
end; $$; end; $$;
/* create trigger insertTriggerForHsOfficeContact_tg
An AFTER INSERT TRIGGER which creates the role structure for a new customer. after insert on hs_office_contact
*/
create trigger createRbacRolesForHsOfficeContact_Trigger
after insert
on hs_office_contact
for each row for each row
execute procedure createRbacRolesForHsOfficeContact(); execute procedure insertTriggerForHsOfficeContact_tf();
--// --//
-- ============================================================================
--changeset hs-office-contact-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_contact permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
begin
call defineContext('create INSERT INTO hs_office_contact permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_contact'),
globalGuest());
END LOOP;
END;
$$;
/**
Adds hs_office_contact INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_contact_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_contact'),
globalGuest());
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_office_contact_global_insert_tg
after insert on global
for each row
execute procedure hs_office_contact_global_insert_tf();
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-contact-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-contact-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_contact', $idName$ call generateRbacIdentityViewFromProjection('hs_office_contact',
target.label $idName$
label
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-contact-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-contact-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_contact', 'target.label', call generateRbacRestrictedView('hs_office_contact',
$orderBy$
label
$orderBy$,
$updates$ $updates$
label = new.label, label = new.label,
postalAddress = new.postalAddress, postalAddress = new.postalAddress,
emailAddresses = new.emailAddresses, emailAddresses = new.emailAddresses,
phoneNumbers = new.phoneNumbers phoneNumbers = new.phoneNumbers
$updates$); $updates$);
--/
-- ============================================================================
--changeset hs-office-contact-rbac-NEW-CONTACT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-contact and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid;
begin
call defineContext('granting global new-contact permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-contact']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeContactNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-contact not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_contact_insert_trigger
before insert
on hs_office_contact
for each row
-- TODO.spec: who is allowed to create new contacts
when ( not hasAssumedRole() )
execute procedure addHsOfficeContactNotAllowedForCurrentSubjects();
--// --//

View File

@ -22,7 +22,6 @@ create table if not exists hs_office_person
givenName varchar(48), givenName varchar(48),
familyName varchar(48) familyName varchar(48)
); );
--//
-- ============================================================================ -- ============================================================================

View File

@ -1,126 +0,0 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-office-person-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_person');
--//
-- ============================================================================
--changeset hs-office-person-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficePerson', 'hs_office_person');
--//
-- ============================================================================
--changeset hs-office-person-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficePerson(
NEW hs_office_person
)
language plpgsql as $$
declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
perform createRoleWithGrants(
hsOfficePersonOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficePersonAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficePersonOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficePersonReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficePersonAdmin(NEW)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_person row.
*/
create or replace function insertTriggerForHsOfficePerson_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficePerson(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficePerson_tg
after insert on hs_office_person
for each row
execute procedure insertTriggerForHsOfficePerson_tf();
--//
-- ============================================================================
--changeset hs-office-person-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_person,
where only global-admin has that permission.
*/
create or replace function hs_office_person_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_person not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_person_insert_permission_check_tg
before insert on hs_office_person
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_person_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-person-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_person',
$idName$
concat(tradeName, familyName, givenName)
$idName$);
--//
-- ============================================================================
--changeset hs-office-person-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_person',
$orderBy$
concat(tradeName, familyName, givenName)
$orderBy$,
$updates$
personType = new.personType,
tradeName = new.tradeName,
givenName = new.givenName,
familyName = new.familyName
$updates$);
--//

View File

@ -21,6 +21,7 @@ subgraph person["`**person**`"]
subgraph person:permissions[ ] subgraph person:permissions[ ]
style person:permissions fill:#dd4901,stroke:white style person:permissions fill:#dd4901,stroke:white
perm:person:INSERT{{person:INSERT}}
perm:person:DELETE{{person:DELETE}} perm:person:DELETE{{person:DELETE}}
perm:person:UPDATE{{person:UPDATE}} perm:person:UPDATE{{person:UPDATE}}
perm:person:SELECT{{person:SELECT}} perm:person:SELECT{{person:SELECT}}
@ -36,6 +37,7 @@ role:person:owner ==> role:person:admin
role:person:admin ==> role:person:referrer role:person:admin ==> role:person:referrer
%% granting permissions to roles %% granting permissions to roles
role:global:guest ==> perm:person:INSERT
role:person:owner ==> perm:person:DELETE role:person:owner ==> perm:person:DELETE
role:person:admin ==> perm:person:UPDATE role:person:admin ==> perm:person:UPDATE
role:person:referrer ==> perm:person:SELECT role:person:referrer ==> perm:person:SELECT

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-person-rbac-OBJECT:1 endDelimiter:--//
@ -15,74 +17,125 @@ call generateRbacRoleDescriptors('hsOfficePerson', 'hs_office_person');
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-person-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates the roles and their assignments for a new person for the AFTER INSERT TRIGGER. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function createRbacRolesForHsOfficePerson()
create or replace procedure buildRbacSystemForHsOfficePerson(
NEW hs_office_person
)
language plpgsql as $$
declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
perform createRoleWithGrants(
hsOfficePersonOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficePersonAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficePersonOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficePersonReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficePersonAdmin(NEW)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_person row.
*/
create or replace function insertTriggerForHsOfficePerson_tf()
returns trigger returns trigger
language plpgsql language plpgsql
strict as $$ strict as $$
begin begin
if TG_OP <> 'INSERT' then call buildRbacSystemForHsOfficePerson(NEW);
raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if;
perform createRoleWithGrants(
hsOfficePersonOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()],
grantedByRole => globalAdmin()
);
-- TODO: who is admin? the person itself? is it allowed for the person itself or a representative to update the data?
perform createRoleWithGrants(
hsOfficePersonAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficePersonOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficePersonTenant(NEW),
incomingSuperRoles => array[hsOfficePersonAdmin(NEW)]
);
perform createRoleWithGrants(
hsOfficePersonGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficePersonTenant(NEW)]
);
return NEW; return NEW;
end; $$; end; $$;
/* create trigger insertTriggerForHsOfficePerson_tg
An AFTER INSERT TRIGGER which creates the role structure for a new customer. after insert on hs_office_person
*/
create trigger createRbacRolesForHsOfficePerson_Trigger
after insert
on hs_office_person
for each row for each row
execute procedure createRbacRolesForHsOfficePerson(); execute procedure insertTriggerForHsOfficePerson_tf();
--// --//
-- ============================================================================
--changeset hs-office-person-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_person permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
begin
call defineContext('create INSERT INTO hs_office_person permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_person'),
globalGuest());
END LOOP;
END;
$$;
/**
Adds hs_office_person INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_person_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_person'),
globalGuest());
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_office_person_global_insert_tg
after insert on global
for each row
execute procedure hs_office_person_global_insert_tf();
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-person-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_person', $idName$
concat(target.tradeName, target.familyName, target.givenName) call generateRbacIdentityViewFromProjection('hs_office_person',
$idName$
concat(tradeName, familyName, givenName)
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-person-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_person', 'concat(target.tradeName, target.familyName, target.givenName)', call generateRbacRestrictedView('hs_office_person',
$orderBy$
concat(tradeName, familyName, givenName)
$orderBy$,
$updates$ $updates$
personType = new.personType, personType = new.personType,
tradeName = new.tradeName, tradeName = new.tradeName,
@ -91,49 +144,3 @@ call generateRbacRestrictedView('hs_office_person', 'concat(target.tradeName, ta
$updates$); $updates$);
--// --//
-- ============================================================================
--changeset hs-office-person-rbac-NEW-PERSON:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-person and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-person permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-person']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficePersonNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-person not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_person_insert_trigger
before insert
on hs_office_person
for each row
-- TODO.spec: who is allowed to create new persons
when ( not hasAssumedRole() )
execute procedure addHsOfficePersonNotAllowedForCurrentSubjects();
--//

View File

@ -28,7 +28,7 @@ begin
call defineContext(currentTask, null, emailAddr); call defineContext(currentTask, null, emailAddr);
execute format('set local hsadminng.currentTask to %L', currentTask); execute format('set local hsadminng.currentTask to %L', currentTask);
raise notice 'creating test person: %', fullName; raise notice 'creating test person: % by %', fullName, emailAddr;
insert insert
into hs_office_person (persontype, tradename, givenname, familyname) into hs_office_person (persontype, tradename, givenname, familyname)
values (newPersonType, newTradeName, newGivenName, newFamilyName); values (newPersonType, newTradeName, newGivenName, newFamilyName);
@ -67,9 +67,10 @@ do language plpgsql $$
call createHsOfficePersonTestData('NP', null, 'Fouler', 'Ellie'); call createHsOfficePersonTestData('NP', null, 'Fouler', 'Ellie');
call createHsOfficePersonTestData('LP', 'Second e.K.', 'Smith', 'Peter'); call createHsOfficePersonTestData('LP', 'Second e.K.', 'Smith', 'Peter');
call createHsOfficePersonTestData('IF', 'Third OHG'); call createHsOfficePersonTestData('IF', 'Third OHG');
call createHsOfficePersonTestData('IF', 'Fourth eG'); call createHsOfficePersonTestData('LP', 'Fourth eG');
call createHsOfficePersonTestData('UF', 'Erben Bessler', 'Mel', 'Bessler'); call createHsOfficePersonTestData('UF', 'Erben Bessler', 'Mel', 'Bessler');
call createHsOfficePersonTestData('NP', null, 'Bessler', 'Anita'); call createHsOfficePersonTestData('NP', null, 'Bessler', 'Anita');
call createHsOfficePersonTestData('NP', null, 'Bessler', 'Bert');
call createHsOfficePersonTestData('NP', null, 'Winkler', 'Paul'); call createHsOfficePersonTestData('NP', null, 'Winkler', 'Paul');
end; end;
$$; $$;

View File

@ -1,100 +0,0 @@
### rbac relation
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph holderPerson["`**holderPerson**`"]
direction TB
style holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph holderPerson:roles[ ]
style holderPerson:roles fill:#99bcdb,stroke:white
role:holderPerson:owner[[holderPerson:owner]]
role:holderPerson:admin[[holderPerson:admin]]
role:holderPerson:referrer[[holderPerson:referrer]]
end
end
subgraph anchorPerson["`**anchorPerson**`"]
direction TB
style anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph anchorPerson:roles[ ]
style anchorPerson:roles fill:#99bcdb,stroke:white
role:anchorPerson:owner[[anchorPerson:owner]]
role:anchorPerson:admin[[anchorPerson:admin]]
role:anchorPerson:referrer[[anchorPerson:referrer]]
end
end
subgraph contact["`**contact**`"]
direction TB
style contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph contact:roles[ ]
style contact:roles fill:#99bcdb,stroke:white
role:contact:owner[[contact:owner]]
role:contact:admin[[contact:admin]]
role:contact:referrer[[contact:referrer]]
end
end
subgraph relation["`**relation**`"]
direction TB
style relation fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph relation:roles[ ]
style relation:roles fill:#dd4901,stroke:white
role:relation:owner[[relation:owner]]
role:relation:admin[[relation:admin]]
role:relation:agent[[relation:agent]]
role:relation:tenant[[relation:tenant]]
end
subgraph relation:permissions[ ]
style relation:permissions fill:#dd4901,stroke:white
perm:relation:DELETE{{relation:DELETE}}
perm:relation:UPDATE{{relation:UPDATE}}
perm:relation:SELECT{{relation:SELECT}}
end
end
%% granting roles to users
user:creator ==> role:relation:owner
%% granting roles to roles
role:global:admin -.-> role:anchorPerson:owner
role:anchorPerson:owner -.-> role:anchorPerson:admin
role:anchorPerson:admin -.-> role:anchorPerson:referrer
role:global:admin -.-> role:holderPerson:owner
role:holderPerson:owner -.-> role:holderPerson:admin
role:holderPerson:admin -.-> role:holderPerson:referrer
role:global:admin -.-> role:contact:owner
role:contact:owner -.-> role:contact:admin
role:contact:admin -.-> role:contact:referrer
role:global:admin ==> role:relation:owner
role:relation:owner ==> role:relation:admin
role:anchorPerson:admin ==> role:relation:admin
role:relation:admin ==> role:relation:agent
role:holderPerson:admin ==> role:relation:agent
role:relation:agent ==> role:relation:tenant
role:holderPerson:admin ==> role:relation:tenant
role:contact:admin ==> role:relation:tenant
role:relation:tenant ==> role:anchorPerson:referrer
role:relation:tenant ==> role:holderPerson:referrer
role:relation:tenant ==> role:contact:referrer
%% granting permissions to roles
role:relation:owner ==> perm:relation:DELETE
role:relation:admin ==> perm:relation:UPDATE
role:relation:tenant ==> perm:relation:SELECT
```

View File

@ -1,191 +0,0 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-office-relation-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_relation');
--//
-- ============================================================================
--changeset hs-office-relation-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficeRelation', 'hs_office_relation');
--//
-- ============================================================================
--changeset hs-office-relation-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficeRelation(
NEW hs_office_relation
)
language plpgsql as $$
declare
newHolderPerson hs_office_person;
newAnchorPerson hs_office_person;
newContact hs_office_contact;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_person WHERE uuid = NEW.holderUuid INTO newHolderPerson;
SELECT * FROM hs_office_person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson;
SELECT * FROM hs_office_contact WHERE uuid = NEW.contactUuid INTO newContact;
perform createRoleWithGrants(
hsOfficeRelationOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficeRelationAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[
hsOfficePersonAdmin(newAnchorPerson),
hsOfficeRelationOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeRelationAgent(NEW),
incomingSuperRoles => array[
hsOfficePersonAdmin(newHolderPerson),
hsOfficeRelationAdmin(NEW)]
);
perform createRoleWithGrants(
hsOfficeRelationTenant(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeContactAdmin(newContact),
hsOfficePersonAdmin(newHolderPerson),
hsOfficeRelationAgent(NEW)],
outgoingSubRoles => array[
hsOfficeContactReferrer(newContact),
hsOfficePersonReferrer(newAnchorPerson),
hsOfficePersonReferrer(newHolderPerson)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_relation row.
*/
create or replace function insertTriggerForHsOfficeRelation_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeRelation(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeRelation_tg
after insert on hs_office_relation
for each row
execute procedure insertTriggerForHsOfficeRelation_tf();
--//
-- ============================================================================
--changeset hs-office-relation-rbac-update-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
*/
create or replace procedure updateRbacRulesForHsOfficeRelation(
OLD hs_office_relation,
NEW hs_office_relation
)
language plpgsql as $$
begin
if NEW.contactUuid is distinct from OLD.contactUuid then
delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid;
call buildRbacSystemForHsOfficeRelation(NEW);
end if;
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_relation row.
*/
create or replace function updateTriggerForHsOfficeRelation_tf()
returns trigger
language plpgsql
strict as $$
begin
call updateRbacRulesForHsOfficeRelation(OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerForHsOfficeRelation_tg
after update on hs_office_relation
for each row
execute procedure updateTriggerForHsOfficeRelation_tf();
--//
-- ============================================================================
--changeset hs-office-relation-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_relation,
where only global-admin has that permission.
*/
create or replace function hs_office_relation_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_relation not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_relation_insert_permission_check_tg
before insert on hs_office_relation
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_relation_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-relation-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_relation',
$idName$
(select idName from hs_office_person_iv p where p.uuid = anchorUuid)
|| '-with-' || target.type || '-'
|| (select idName from hs_office_person_iv p where p.uuid = holderUuid)
$idName$);
--//
-- ============================================================================
--changeset hs-office-relation-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_relation',
$orderBy$
(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)
$orderBy$,
$updates$
contactUuid = new.contactUuid
$updates$);
--//

View File

@ -1,44 +1,102 @@
### hs_office_relation RBAC ### rbac relation
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
subgraph global subgraph holderPerson["`**holderPerson**`"]
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeContact
direction TB direction TB
style hsOfficeContact fill:#eee style holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeContact.admin[contact.admin] subgraph holderPerson:roles[ ]
--> role:hsOfficeContact.tenant[contact.tenant] style holderPerson:roles fill:#99bcdb,stroke:white
--> role:hsOfficeContact.guest[contact.guest]
role:holderPerson:owner[[holderPerson:owner]]
role:holderPerson:admin[[holderPerson:admin]]
role:holderPerson:referrer[[holderPerson:referrer]]
end
end end
subgraph hsOfficePerson subgraph anchorPerson["`**anchorPerson**`"]
direction TB direction TB
style hsOfficePerson fill:#eee style anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficePerson.admin[person.admin] subgraph anchorPerson:roles[ ]
--> role:hsOfficePerson.tenant[person.tenant] style anchorPerson:roles fill:#99bcdb,stroke:white
--> role:hsOfficePerson.guest[person.guest]
role:anchorPerson:owner[[anchorPerson:owner]]
role:anchorPerson:admin[[anchorPerson:admin]]
role:anchorPerson:referrer[[anchorPerson:referrer]]
end
end end
subgraph hsOfficeRelation subgraph contact["`**contact**`"]
direction TB
style contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficePerson#anchor.admin[person#anchor.admin] subgraph contact:roles[ ]
--- role:hsOfficePerson.admin style contact:roles fill:#99bcdb,stroke:white
role:hsOfficeRelation.owner[relation.owner] role:contact:owner[[contact:owner]]
%% permissions role:contact:admin[[contact:admin]]
role:hsOfficeRelation.owner --> perm:hsOfficeRelation.*{{relation.*}} role:contact:referrer[[contact:referrer]]
%% incoming end
role:global.admin ---> role:hsOfficeRelation.owner
role:hsOfficePersonAdmin#anchor.admin
end end
subgraph relation["`**relation**`"]
direction TB
style relation fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph relation:roles[ ]
style relation:roles fill:#dd4901,stroke:white
role:relation:owner[[relation:owner]]
role:relation:admin[[relation:admin]]
role:relation:agent[[relation:agent]]
role:relation:tenant[[relation:tenant]]
end
subgraph relation:permissions[ ]
style relation:permissions fill:#dd4901,stroke:white
perm:relation:DELETE{{relation:DELETE}}
perm:relation:UPDATE{{relation:UPDATE}}
perm:relation:SELECT{{relation:SELECT}}
perm:relation:INSERT{{relation:INSERT}}
end
end
%% granting roles to users
user:creator ==> role:relation:owner
%% granting roles to roles
role:global:admin -.-> role:anchorPerson:owner
role:anchorPerson:owner -.-> role:anchorPerson:admin
role:anchorPerson:admin -.-> role:anchorPerson:referrer
role:global:admin -.-> role:holderPerson:owner
role:holderPerson:owner -.-> role:holderPerson:admin
role:holderPerson:admin -.-> role:holderPerson:referrer
role:global:admin -.-> role:contact:owner
role:contact:owner -.-> role:contact:admin
role:contact:admin -.-> role:contact:referrer
role:global:admin ==> role:relation:owner
role:relation:owner ==> role:relation:admin
role:anchorPerson:admin ==> role:relation:admin
role:relation:admin ==> role:relation:agent
role:holderPerson:admin ==> role:relation:agent
role:relation:agent ==> role:relation:tenant
role:holderPerson:admin ==> role:relation:tenant
role:contact:admin ==> role:relation:tenant
role:relation:tenant ==> role:anchorPerson:referrer
role:relation:tenant ==> role:holderPerson:referrer
role:relation:tenant ==> role:contact:referrer
%% granting permissions to roles
role:relation:owner ==> perm:relation:DELETE
role:relation:admin ==> perm:relation:UPDATE
role:relation:tenant ==> perm:relation:SELECT
role:anchorPerson:admin ==> perm:relation:INSERT
``` ```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-relation-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-relation-rbac-OBJECT:1 endDelimiter:--//
@ -15,178 +17,255 @@ call generateRbacRoleDescriptors('hsOfficeRelation', 'hs_office_relation');
-- ============================================================================ -- ============================================================================
--changeset hs-office-relation-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-relation-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates and updates the roles and their assignments for relation entities. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function hsOfficeRelationRbacRolesTrigger() create or replace procedure buildRbacSystemForHsOfficeRelation(
returns trigger NEW hs_office_relation
language plpgsql )
strict as $$ language plpgsql as $$
declare declare
hsOfficeRelationTenant RbacRoleDescriptor; newHolderPerson hs_office_person;
newAnchor hs_office_person; newAnchorPerson hs_office_person;
newHolder hs_office_person; newContact hs_office_contact;
oldContact hs_office_contact;
newContact hs_office_contact;
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
hsOfficeRelationTenant := hsOfficeRelationTenant(NEW); SELECT * FROM hs_office_person WHERE uuid = NEW.holderUuid INTO newHolderPerson;
assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid);
select * from hs_office_person as p where p.uuid = NEW.anchorUuid into newAnchor; SELECT * FROM hs_office_person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson;
select * from hs_office_person as p where p.uuid = NEW.holderUuid into newHolder; assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid);
select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact;
if TG_OP = 'INSERT' then SELECT * FROM hs_office_contact WHERE uuid = NEW.contactUuid INTO newContact;
assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid);
perform createRoleWithGrants(
hsOfficeRelationOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[
globalAdmin(),
hsOfficePersonAdmin(newAnchor)]
);
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeRelationAdmin(NEW), hsOfficeRelationOwner(NEW),
permissions => array['UPDATE'], permissions => array['DELETE'],
incomingSuperRoles => array[hsOfficeRelationOwner(NEW)] incomingSuperRoles => array[globalAdmin()],
); userUuids => array[currentUserUuid()]
);
-- the tenant role for those related users who can view the data perform createRoleWithGrants(
perform createRoleWithGrants( hsOfficeRelationAdmin(NEW),
hsOfficeRelationTenant, permissions => array['UPDATE'],
permissions => array['SELECT'], incomingSuperRoles => array[
incomingSuperRoles => array[ hsOfficePersonAdmin(newAnchorPerson),
hsOfficeRelationAdmin(NEW), hsOfficeRelationOwner(NEW)]
hsOfficePersonAdmin(newAnchor), );
hsOfficePersonAdmin(newHolder),
hsOfficeContactAdmin(newContact)],
outgoingSubRoles => array[
hsOfficePersonTenant(newAnchor),
hsOfficePersonTenant(newHolder),
hsOfficeContactTenant(newContact)]
);
-- anchor and holder admin roles need each others tenant role perform createRoleWithGrants(
-- to be able to see the joined relation hsOfficeRelationAgent(NEW),
-- TODO: this can probably be avoided through agent+guest roles incomingSuperRoles => array[
call grantRoleToRole(hsOfficePersonTenant(newAnchor), hsOfficePersonAdmin(newHolder)); hsOfficePersonAdmin(newHolderPerson),
call grantRoleToRole(hsOfficePersonTenant(newHolder), hsOfficePersonAdmin(newAnchor)); hsOfficeRelationAdmin(NEW)]
call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newHolder), hsOfficeContactAdmin(newContact)); );
elsif TG_OP = 'UPDATE' then perform createRoleWithGrants(
hsOfficeRelationTenant(NEW),
if OLD.contactUuid <> NEW.contactUuid then permissions => array['SELECT'],
-- nothing but the contact can be updated, incomingSuperRoles => array[
-- in other cases, a new relation needs to be created and the old updated hsOfficeContactAdmin(newContact),
hsOfficePersonAdmin(newHolderPerson),
select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact; hsOfficeRelationAgent(NEW)],
outgoingSubRoles => array[
call revokeRoleFromRole( hsOfficeRelationTenant, hsOfficeContactAdmin(oldContact) ); hsOfficeContactReferrer(newContact),
call grantRoleToRole( hsOfficeRelationTenant, hsOfficeContactAdmin(newContact) ); hsOfficePersonReferrer(newAnchorPerson),
hsOfficePersonReferrer(newHolderPerson)]
call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationTenant ); );
call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationTenant );
end if;
else
raise exception 'invalid usage of TRIGGER';
end if;
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
end; $$; end; $$;
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new customer. AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_relation row.
*/ */
create trigger createRbacRolesForHsOfficeRelation_Trigger
after insert
on hs_office_relation
for each row
execute procedure hsOfficeRelationRbacRolesTrigger();
/* create or replace function insertTriggerForHsOfficeRelation_tf()
An AFTER UPDATE TRIGGER which updates the role structure of a customer. returns trigger
*/ language plpgsql
create trigger updateRbacRolesForHsOfficeRelation_Trigger strict as $$
after update begin
on hs_office_relation call buildRbacSystemForHsOfficeRelation(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeRelation_tg
after insert on hs_office_relation
for each row for each row
execute procedure hsOfficeRelationRbacRolesTrigger(); execute procedure insertTriggerForHsOfficeRelation_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-relation-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-relation-rbac-update-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_relation', $idName$
(select idName from hs_office_person_iv p where p.uuid = target.anchorUuid) /*
|| '-with-' || target.type || '-' || Called from the AFTER UPDATE TRIGGER to re-wire the grants.
(select idName from hs_office_person_iv p where p.uuid = target.holderUuid) */
$idName$);
create or replace procedure updateRbacRulesForHsOfficeRelation(
OLD hs_office_relation,
NEW hs_office_relation
)
language plpgsql as $$
declare
oldHolderPerson hs_office_person;
newHolderPerson hs_office_person;
oldAnchorPerson hs_office_person;
newAnchorPerson hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_person WHERE uuid = OLD.holderUuid INTO oldHolderPerson;
assert oldHolderPerson.uuid is not null, format('oldHolderPerson must not be null for OLD.holderUuid = %s', OLD.holderUuid);
SELECT * FROM hs_office_person WHERE uuid = NEW.holderUuid INTO newHolderPerson;
assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid);
SELECT * FROM hs_office_person WHERE uuid = OLD.anchorUuid INTO oldAnchorPerson;
assert oldAnchorPerson.uuid is not null, format('oldAnchorPerson must not be null for OLD.anchorUuid = %s', OLD.anchorUuid);
SELECT * FROM hs_office_person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson;
assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid);
SELECT * FROM hs_office_contact WHERE uuid = OLD.contactUuid INTO oldContact;
assert oldContact.uuid is not null, format('oldContact must not be null for OLD.contactUuid = %s', OLD.contactUuid);
SELECT * FROM hs_office_contact WHERE uuid = NEW.contactUuid INTO newContact;
assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid);
if NEW.contactUuid <> OLD.contactUuid then
call revokeRoleFromRole(hsOfficeRelationTenant(OLD), hsOfficeContactAdmin(oldContact));
call grantRoleToRole(hsOfficeRelationTenant(NEW), hsOfficeContactAdmin(newContact));
call revokeRoleFromRole(hsOfficeContactReferrer(oldContact), hsOfficeRelationTenant(OLD));
call grantRoleToRole(hsOfficeContactReferrer(newContact), hsOfficeRelationTenant(NEW));
end if;
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_relation row.
*/
create or replace function updateTriggerForHsOfficeRelation_tf()
returns trigger
language plpgsql
strict as $$
begin
call updateRbacRulesForHsOfficeRelation(OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerForHsOfficeRelation_tg
after update on hs_office_relation
for each row
execute procedure updateTriggerForHsOfficeRelation_tf();
--// --//
-- ============================================================================
--changeset hs-office-relation-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_relation permissions for the related hs_office_person rows.
*/
do language plpgsql $$
declare
row hs_office_person;
begin
call defineContext('create INSERT INTO hs_office_relation permissions for the related hs_office_person rows');
FOR row IN SELECT * FROM hs_office_person
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_relation'),
hsOfficePersonAdmin(row));
END LOOP;
END;
$$;
/**
Adds hs_office_relation INSERT permission to specified role of new hs_office_person rows.
*/
create or replace function hs_office_relation_hs_office_person_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_relation'),
hsOfficePersonAdmin(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_office_relation_hs_office_person_insert_tg
after insert on hs_office_person
for each row
execute procedure hs_office_relation_hs_office_person_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_relation,
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_office_relation_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_relation not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_relation_insert_permission_check_tg
before insert on hs_office_relation
for each row
when ( not hasInsertPermission(NEW.anchorUuid, 'INSERT', 'hs_office_relation') )
execute procedure hs_office_relation_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-relation-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_relation',
$idName$
(select idName from hs_office_person_iv p where p.uuid = anchorUuid)
|| '-with-' || target.type || '-'
|| (select idName from hs_office_person_iv p where p.uuid = holderUuid)
$idName$);
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-relation-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-relation-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_relation', call generateRbacRestrictedView('hs_office_relation',
'(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)', $orderBy$
(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)
$orderBy$,
$updates$ $updates$
contactUuid = new.contactUuid contactUuid = new.contactUuid
$updates$); $updates$);
--// --//
-- TODO: exception if one tries to amend any other column
-- ============================================================================
--changeset hs-office-relation-rbac-NEW-RELATHIONSHIP:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-relation and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-relation permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-relation']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeRelationNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-relation not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_relation_insert_trigger
before insert
on hs_office_relation
for each row
-- TODO.spec: who is allowed to create new relations
when ( not hasAssumedRole() )
execute procedure addHsOfficeRelationNotAllowedForCurrentSubjects();
--//

View File

@ -11,7 +11,7 @@
create or replace procedure createHsOfficeRelationTestData( create or replace procedure createHsOfficeRelationTestData(
holderPersonName varchar, holderPersonName varchar,
relationType HsOfficeRelationType, relationType HsOfficeRelationType,
anchorPersonTradeName varchar, anchorPersonName varchar,
contactLabel varchar, contactLabel varchar,
mark varchar default null) mark varchar default null)
language plpgsql as $$ language plpgsql as $$
@ -23,24 +23,28 @@ declare
contact hs_office_contact; contact hs_office_contact;
begin begin
idName := cleanIdentifier( anchorPersonTradeName || '-' || holderPersonName); idName := cleanIdentifier( anchorPersonName || '-' || holderPersonName);
currentTask := 'creating relation test-data ' || idName; currentTask := 'creating relation test-data ' || idName;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin'); call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask); execute format('set local hsadminng.currentTask to %L', currentTask);
select p.* from hs_office_person p where p.tradeName = anchorPersonTradeName into anchorPerson; select p.*
into anchorPerson
from hs_office_person p
where p.tradeName = anchorPersonName or p.familyName = anchorPersonName;
if anchorPerson is null then if anchorPerson is null then
raise exception 'anchorPerson "%" not found', anchorPersonTradeName; raise exception 'anchorPerson "%" not found', anchorPersonName;
end if; end if;
select p.* from hs_office_person p select p.*
where p.tradeName = holderPersonName or p.familyName = holderPersonName into holderPerson
into holderPerson; from hs_office_person p
where p.tradeName = holderPersonName or p.familyName = holderPersonName;
if holderPerson is null then if holderPerson is null then
raise exception 'holderPerson "%" not found', holderPersonName; raise exception 'holderPerson "%" not found', holderPersonName;
end if; end if;
select c.* from hs_office_contact c where c.label = contactLabel into contact; select c.* into contact from hs_office_contact c where c.label = contactLabel;
if contact is null then if contact is null then
raise exception 'contact "%" not found', contactLabel; raise exception 'contact "%" not found', contactLabel;
end if; end if;
@ -87,17 +91,22 @@ do language plpgsql $$
begin begin
call createHsOfficeRelationTestData('First GmbH', 'PARTNER', 'Hostsharing eG', 'first contact'); call createHsOfficeRelationTestData('First GmbH', 'PARTNER', 'Hostsharing eG', 'first contact');
call createHsOfficeRelationTestData('Firby', 'REPRESENTATIVE', 'First GmbH', 'first contact'); call createHsOfficeRelationTestData('Firby', 'REPRESENTATIVE', 'First GmbH', 'first contact');
call createHsOfficeRelationTestData('First GmbH', 'DEBITOR', 'First GmbH', 'first contact');
call createHsOfficeRelationTestData('Second e.K.', 'PARTNER', 'Hostsharing eG', 'second contact'); call createHsOfficeRelationTestData('Second e.K.', 'PARTNER', 'Hostsharing eG', 'second contact');
call createHsOfficeRelationTestData('Smith', 'REPRESENTATIVE', 'Second e.K.', 'second contact'); call createHsOfficeRelationTestData('Smith', 'REPRESENTATIVE', 'Second e.K.', 'second contact');
call createHsOfficeRelationTestData('Second e.K.', 'DEBITOR', 'Second e.K.', 'second contact');
call createHsOfficeRelationTestData('Third OHG', 'PARTNER', 'Hostsharing eG', 'third contact'); call createHsOfficeRelationTestData('Third OHG', 'PARTNER', 'Hostsharing eG', 'third contact');
call createHsOfficeRelationTestData('Tucker', 'REPRESENTATIVE', 'Third OHG', 'third contact'); call createHsOfficeRelationTestData('Tucker', 'REPRESENTATIVE', 'Third OHG', 'third contact');
call createHsOfficeRelationTestData('Third OHG', 'DEBITOR', 'Third OHG', 'third contact');
call createHsOfficeRelationTestData('Fourth eG', 'PARTNER', 'Hostsharing eG', 'fourth contact'); call createHsOfficeRelationTestData('Fourth eG', 'PARTNER', 'Hostsharing eG', 'fourth contact');
call createHsOfficeRelationTestData('Fouler', 'REPRESENTATIVE', 'Third OHG', 'third contact'); call createHsOfficeRelationTestData('Fouler', 'REPRESENTATIVE', 'Third OHG', 'third contact');
call createHsOfficeRelationTestData('Third OHG', 'DEBITOR', 'Third OHG', 'third contact');
call createHsOfficeRelationTestData('Smith', 'PARTNER', 'Hostsharing eG', 'sixth contact'); call createHsOfficeRelationTestData('Smith', 'PARTNER', 'Hostsharing eG', 'sixth contact');
call createHsOfficeRelationTestData('Smith', 'DEBITOR', 'Smith', 'third contact');
call createHsOfficeRelationTestData('Smith', 'SUBSCRIBER', 'Third OHG', 'third contact', 'members-announce'); call createHsOfficeRelationTestData('Smith', 'SUBSCRIBER', 'Third OHG', 'third contact', 'members-announce');
end; end;
$$; $$;

View File

@ -33,23 +33,20 @@ create table hs_office_partner
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
partnerNumber numeric(5) unique not null, partnerNumber numeric(5) unique not null,
partnerRelUuid uuid not null references hs_office_relation(uuid), -- TODO: delete in after delete trigger partnerRelUuid uuid not null references hs_office_relation(uuid), -- deleted in after delete trigger
personUuid uuid not null references hs_office_person(uuid), -- TODO: remove, replaced by partnerRelUuid
contactUuid uuid not null references hs_office_contact(uuid), -- TODO: remove, replaced by partnerRelUuid
detailsUuid uuid not null references hs_office_partner_details(uuid) -- deleted in after delete trigger detailsUuid uuid not null references hs_office_partner_details(uuid) -- deleted in after delete trigger
); );
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-DELETE-DETAILS-TRIGGER:1 endDelimiter:--// --changeset hs-office-partner-DELETE-DEPENDENTS-TRIGGER:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/** /**
Trigger function to delete related details of a partner to delete. Trigger function to delete related details of a partner to delete.
*/ */
create or replace function deleteHsOfficeDetailsOnPartnerDelete() create or replace function deleteHsOfficeDependentsOnPartnerDelete()
returns trigger returns trigger
language PLPGSQL language PLPGSQL
as $$ as $$
@ -61,17 +58,24 @@ begin
if counter = 0 then if counter = 0 then
raise exception 'partner details % could not be deleted', OLD.detailsUuid; raise exception 'partner details % could not be deleted', OLD.detailsUuid;
end if; end if;
DELETE FROM hs_office_relation r WHERE r.uuid = OLD.partnerRelUuid;
GET DIAGNOSTICS counter = ROW_COUNT;
if counter = 0 then
raise exception 'partner relation % could not be deleted', OLD.partnerRelUuid;
end if;
RETURN OLD; RETURN OLD;
end; $$; end; $$;
/** /**
Triggers deletion of related details of a partner to delete. Triggers deletion of related rows of a partner to delete.
*/ */
create trigger hs_office_partner_delete_details_trigger create trigger hs_office_partner_delete_dependents_trigger
after delete after delete
on hs_office_partner on hs_office_partner
for each row for each row
execute procedure deleteHsOfficeDetailsOnPartnerDelete(); execute procedure deleteHsOfficeDependentsOnPartnerDelete();
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-MAIN-TABLE-JOURNAL:1 endDelimiter:--// --changeset hs-office-partner-MAIN-TABLE-JOURNAL:1 endDelimiter:--//

View File

@ -1,158 +0,0 @@
### rbac partner
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partner["`**partner**`"]
direction TB
style partner fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph partner:permissions[ ]
style partner:permissions fill:#dd4901,stroke:white
perm:partner:INSERT{{partner:INSERT}}
perm:partner:DELETE{{partner:DELETE}}
perm:partner:UPDATE{{partner:UPDATE}}
perm:partner:SELECT{{partner:SELECT}}
end
subgraph partnerRel["`**partnerRel**`"]
direction TB
style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph partnerRel:roles[ ]
style partnerRel:roles fill:#99bcdb,stroke:white
role:partnerRel:owner[[partnerRel:owner]]
role:partnerRel:admin[[partnerRel:admin]]
role:partnerRel:agent[[partnerRel:agent]]
role:partnerRel:tenant[[partnerRel:tenant]]
end
end
end
subgraph partnerDetails["`**partnerDetails**`"]
direction TB
style partnerDetails fill:#feb28c,stroke:#274d6e,stroke-width:8px
subgraph partnerDetails:permissions[ ]
style partnerDetails:permissions fill:#feb28c,stroke:white
perm:partnerDetails:DELETE{{partnerDetails:DELETE}}
perm:partnerDetails:UPDATE{{partnerDetails:UPDATE}}
perm:partnerDetails:SELECT{{partnerDetails:SELECT}}
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
%% granting roles to roles
role:global:admin -.-> role:partnerRel.anchorPerson:owner
role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer
role:global:admin -.-> role:partnerRel.holderPerson:owner
role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin
role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer
role:global:admin -.-> role:partnerRel.contact:owner
role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin
role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer
role:global:admin -.-> role:partnerRel:owner
role:partnerRel:owner -.-> role:partnerRel:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin
role:partnerRel:admin -.-> role:partnerRel:agent
role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent
role:partnerRel:agent -.-> role:partnerRel:tenant
role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant
role:partnerRel.contact:admin -.-> role:partnerRel:tenant
role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.contact:referrer
%% granting permissions to roles
role:global:admin ==> perm:partner:INSERT
role:partnerRel:admin ==> perm:partner:DELETE
role:partnerRel:agent ==> perm:partner:UPDATE
role:partnerRel:tenant ==> perm:partner:SELECT
role:partnerRel:admin ==> perm:partnerDetails:DELETE
role:partnerRel:agent ==> perm:partnerDetails:UPDATE
role:partnerRel:agent ==> perm:partnerDetails:SELECT
```

View File

@ -1,248 +0,0 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-office-partner-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_partner');
--//
-- ============================================================================
--changeset hs-office-partner-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficePartner', 'hs_office_partner');
--//
-- ============================================================================
--changeset hs-office-partner-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficePartner(
NEW hs_office_partner
)
language plpgsql as $$
declare
newPartnerRel hs_office_relation;
newPartnerDetails hs_office_partner_details;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(newPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel));
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_partner row.
*/
create or replace function insertTriggerForHsOfficePartner_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficePartner(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficePartner_tg
after insert on hs_office_partner
for each row
execute procedure insertTriggerForHsOfficePartner_tf();
--//
-- ============================================================================
--changeset hs-office-partner-rbac-update-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
*/
create or replace procedure updateRbacRulesForHsOfficePartner(
OLD hs_office_partner,
NEW hs_office_partner
)
language plpgsql as $$
declare
oldPartnerRel hs_office_relation;
newPartnerRel hs_office_relation;
oldPartnerDetails hs_office_partner_details;
newPartnerDetails hs_office_partner_details;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_relation WHERE uuid = OLD.partnerRelUuid INTO oldPartnerRel;
assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s', OLD.partnerRelUuid);
SELECT * FROM hs_office_relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
SELECT * FROM hs_office_partner_details WHERE uuid = OLD.detailsUuid INTO oldPartnerDetails;
assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s', OLD.detailsUuid);
SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
if NEW.partnerRelUuid <> OLD.partnerRelUuid then
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationAdmin(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationTenant(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(newPartnerRel));
end if;
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_partner row.
*/
create or replace function updateTriggerForHsOfficePartner_tf()
returns trigger
language plpgsql
strict as $$
begin
call updateRbacRulesForHsOfficePartner(OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerForHsOfficePartner_tg
after update on hs_office_partner
for each row
execute procedure updateTriggerForHsOfficePartner_tf();
--//
-- ============================================================================
--changeset hs-office-partner-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_partner permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO hs_office_partner permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
roleUuid := findRoleId(globalAdmin());
permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_partner');
call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP;
END;
$$;
/**
Adds hs_office_partner INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_partner_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_partner'),
globalAdmin());
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_office_partner_global_insert_tg
after insert on global
for each row
execute procedure hs_office_partner_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_partner,
where only global-admin has that permission.
*/
create or replace function hs_office_partner_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_partner not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_partner_insert_permission_check_tg
before insert on hs_office_partner
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_partner_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromQuery('hs_office_partner',
$idName$
SELECT partner.partnerNumber
|| ':' || (SELECT idName FROM hs_office_person_iv p WHERE p.uuid = partner.personUuid)
|| '-' || (SELECT idName FROM hs_office_contact_iv c WHERE c.uuid = partner.contactUuid)
FROM hs_office_partner AS partner
$idName$);
--//
-- ============================================================================
--changeset hs-office-partner-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_partner',
$orderBy$
SELECT partner.partnerNumber
|| ':' || (SELECT idName FROM hs_office_person_iv p WHERE p.uuid = partner.personUuid)
|| '-' || (SELECT idName FROM hs_office_contact_iv c WHERE c.uuid = partner.contactUuid)
FROM hs_office_partner AS partner
$orderBy$,
$updates$
partnerRelUuid = new.partnerRelUuid,
personUuid = new.personUuid,
contactUuid = new.contactUuid
$updates$);
--//

View File

@ -1,78 +1,158 @@
### hs_office_partner RBAC ### rbac partner
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
subgraph global subgraph partnerRel.contact["`**partnerRel.contact**`"]
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeContact
direction TB direction TB
style hsOfficeContact fill:#eee style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeContact.admin[contact.admin] subgraph partnerRel.contact:roles[ ]
--> role:hsOfficeContact.tenant[contact.tenant] style partnerRel.contact:roles fill:#99bcdb,stroke:white
--> role:hsOfficeContact.guest[contact.guest]
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end end
subgraph hsOfficePerson subgraph partner["`**partner**`"]
direction TB direction TB
style hsOfficePerson fill:#eee style partner fill:#dd4901,stroke:#274d6e,stroke-width:8px
role:hsOfficePerson.admin[person.admin] subgraph partner:permissions[ ]
--> role:hsOfficePerson.tenant[person.tenant] style partner:permissions fill:#dd4901,stroke:white
--> role:hsOfficePerson.guest[person.guest]
perm:partner:INSERT{{partner:INSERT}}
perm:partner:DELETE{{partner:DELETE}}
perm:partner:UPDATE{{partner:UPDATE}}
perm:partner:SELECT{{partner:SELECT}}
end
subgraph partnerRel["`**partnerRel**`"]
direction TB
style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph partnerRel:roles[ ]
style partnerRel:roles fill:#99bcdb,stroke:white
role:partnerRel:owner[[partnerRel:owner]]
role:partnerRel:admin[[partnerRel:admin]]
role:partnerRel:agent[[partnerRel:agent]]
role:partnerRel:tenant[[partnerRel:tenant]]
end
end
end end
subgraph hsOfficePartnerDetails subgraph partnerDetails["`**partnerDetails**`"]
direction TB direction TB
style partnerDetails fill:#feb28c,stroke:#274d6e,stroke-width:8px
perm:hsOfficePartnerDetails.*{{partner.*}}
perm:hsOfficePartnerDetails.edit{{partner.edit}} subgraph partnerDetails:permissions[ ]
perm:hsOfficePartnerDetails.view{{partner.view}} style partnerDetails:permissions fill:#feb28c,stroke:white
perm:partnerDetails:DELETE{{partnerDetails:DELETE}}
perm:partnerDetails:UPDATE{{partnerDetails:UPDATE}}
perm:partnerDetails:SELECT{{partnerDetails:SELECT}}
end
end end
subgraph hsOfficePartner subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
role:hsOfficePartner.owner[partner.owner] style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
%% permissions
role:hsOfficePartner.owner --> perm:hsOfficePartner.*{{partner.*}}
role:hsOfficePartner.owner --> perm:hsOfficePartnerDetails.*{{partner.*}}
%% incoming
role:global.admin ---> role:hsOfficePartner.owner
role:hsOfficePartner.admin[partner.admin]
%% permissions
role:hsOfficePartner.admin --> perm:hsOfficePartner.edit{{partner.edit}}
role:hsOfficePartner.admin --> perm:hsOfficePartnerDetails.edit{{partner.edit}}
%% incoming
role:hsOfficePartner.owner ---> role:hsOfficePartner.admin
%% outgoing
role:hsOfficePartner.admin --> role:hsOfficePerson.tenant
role:hsOfficePartner.admin --> role:hsOfficeContact.tenant
role:hsOfficePartner.agent[partner.agent]
%% permissions
role:hsOfficePartner.agent --> perm:hsOfficePartnerDetails.view{{partner.view}}
%% incoming
role:hsOfficePartner.admin ---> role:hsOfficePartner.agent
role:hsOfficePerson.admin --> role:hsOfficePartner.agent
role:hsOfficeContact.admin --> role:hsOfficePartner.agent
role:hsOfficePartner.tenant[partner.tenant]
%% incoming
role:hsOfficePartner.agent --> role:hsOfficePartner.tenant
%% outgoing
role:hsOfficePartner.tenant --> role:hsOfficePerson.guest
role:hsOfficePartner.tenant --> role:hsOfficeContact.guest
role:hsOfficePartner.guest[partner.guest] subgraph partnerRel.anchorPerson:roles[ ]
%% permissions style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:hsOfficePartner.guest --> perm:hsOfficePartner.view{{partner.view}}
%% incoming role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:hsOfficePartner.tenant --> role:hsOfficePartner.guest role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
%% granting roles to roles
role:global:admin -.-> role:partnerRel.anchorPerson:owner
role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer
role:global:admin -.-> role:partnerRel.holderPerson:owner
role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin
role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer
role:global:admin -.-> role:partnerRel.contact:owner
role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin
role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer
role:global:admin -.-> role:partnerRel:owner
role:partnerRel:owner -.-> role:partnerRel:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin
role:partnerRel:admin -.-> role:partnerRel:agent
role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent
role:partnerRel:agent -.-> role:partnerRel:tenant
role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant
role:partnerRel.contact:admin -.-> role:partnerRel:tenant
role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.contact:referrer
%% granting permissions to roles
role:global:admin ==> perm:partner:INSERT
role:partnerRel:admin ==> perm:partner:DELETE
role:partnerRel:agent ==> perm:partner:UPDATE
role:partnerRel:tenant ==> perm:partner:SELECT
role:partnerRel:admin ==> perm:partnerDetails:DELETE
role:partnerRel:agent ==> perm:partnerDetails:UPDATE
role:partnerRel:agent ==> perm:partnerDetails:SELECT
``` ```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-partner-rbac-OBJECT:1 endDelimiter:--//
@ -15,242 +17,222 @@ call generateRbacRoleDescriptors('hsOfficePartner', 'hs_office_partner');
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-partner-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates and updates the roles and their assignments for partner entities. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function hsOfficePartnerRbacRolesTrigger() create or replace procedure buildRbacSystemForHsOfficePartner(
returns trigger NEW hs_office_partner
language plpgsql )
strict as $$ language plpgsql as $$
declare declare
oldPartnerRel hs_office_relation; newPartnerRel hs_office_relation;
newPartnerRel hs_office_relation; newPartnerDetails hs_office_partner_details;
oldPerson hs_office_person;
newPerson hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_relation as r where r.uuid = NEW.partnerReluuid into newPartnerRel; SELECT * FROM hs_office_relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
select * from hs_office_person as p where p.uuid = NEW.personUuid into newPerson; assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact;
if TG_OP = 'INSERT' then SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
-- === ATTENTION: code generated from related Mermaid flowchart: === call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newPartnerRel));
perform createRoleWithGrants( call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel));
hsOfficePartnerOwner(NEW), call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel));
permissions => array['DELETE'], call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(newPartnerRel));
incomingSuperRoles => array[globalAdmin()] call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel));
);
perform createRoleWithGrants(
hsOfficePartnerAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[
hsOfficePartnerOwner(NEW)],
outgoingSubRoles => array[
hsOfficeRelationTenant(newPartnerRel),
hsOfficePersonTenant(newPerson),
hsOfficeContactTenant(newContact)]
);
perform createRoleWithGrants(
hsOfficePartnerAgent(NEW),
incomingSuperRoles => array[
hsOfficePartnerAdmin(NEW),
hsOfficeRelationAdmin(newPartnerRel),
hsOfficePersonAdmin(newPerson),
hsOfficeContactAdmin(newContact)]
);
perform createRoleWithGrants(
hsOfficePartnerTenant(NEW),
incomingSuperRoles => array[
hsOfficePartnerAgent(NEW)],
outgoingSubRoles => array[
hsOfficeRelationTenant(newPartnerRel),
hsOfficePersonGuest(newPerson),
hsOfficeContactGuest(newContact)]
);
perform createRoleWithGrants(
hsOfficePartnerGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficePartnerTenant(NEW)]
);
-- === END of code generated from Mermaid flowchart. ===
-- Each partner-details entity belong exactly to one partner entity
-- and it makes little sense just to delegate partner-details roles.
-- Therefore, we did not model partner-details roles,
-- but instead just assign extra permissions to existing partner-roles.
--Attention: Cannot be in partner-details because of insert order (partner is not in database yet)
call grantPermissionsToRole(
getRoleId(hsOfficePartnerOwner(NEW)),
createPermissions(NEW.detailsUuid, array ['DELETE'])
);
call grantPermissionsToRole(
getRoleId(hsOfficePartnerAdmin(NEW)),
createPermissions(NEW.detailsUuid, array ['UPDATE'])
);
call grantPermissionsToRole(
-- Yes, here hsOfficePartnerAGENT is used, not hsOfficePartnerTENANT.
-- Do NOT grant view permission on partner-details to hsOfficePartnerTENANT!
-- Otherwise package-admins etc. would be able to read the data.
getRoleId(hsOfficePartnerAgent(NEW)),
createPermissions(NEW.detailsUuid, array ['SELECT'])
);
elsif TG_OP = 'UPDATE' then
if OLD.partnerRelUuid <> NEW.partnerRelUuid then
select * from hs_office_relation as r where r.uuid = OLD.partnerRelUuid into oldPartnerRel;
call revokeRoleFromRole(hsOfficeRelationTenant(oldPartnerRel), hsOfficePartnerAdmin(OLD));
call grantRoleToRole(hsOfficeRelationTenant(newPartnerRel), hsOfficePartnerAdmin(NEW));
call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficeRelationAdmin(oldPartnerRel));
call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficeRelationAdmin(newPartnerRel));
call revokeRoleFromRole(hsOfficeRelationGuest(oldPartnerRel), hsOfficePartnerTenant(OLD));
call grantRoleToRole(hsOfficeRelationGuest(newPartnerRel), hsOfficePartnerTenant(NEW));
end if;
if OLD.personUuid <> NEW.personUuid then
select * from hs_office_person as p where p.uuid = OLD.personUuid into oldPerson;
call revokeRoleFromRole(hsOfficePersonTenant(oldPerson), hsOfficePartnerAdmin(OLD));
call grantRoleToRole(hsOfficePersonTenant(newPerson), hsOfficePartnerAdmin(NEW));
call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficePersonAdmin(oldPerson));
call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficePersonAdmin(newPerson));
call revokeRoleFromRole(hsOfficePersonGuest(oldPerson), hsOfficePartnerTenant(OLD));
call grantRoleToRole(hsOfficePersonGuest(newPerson), hsOfficePartnerTenant(NEW));
end if;
if OLD.contactUuid <> NEW.contactUuid then
select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact;
call revokeRoleFromRole(hsOfficeContactTenant(oldContact), hsOfficePartnerAdmin(OLD));
call grantRoleToRole(hsOfficeContactTenant(newContact), hsOfficePartnerAdmin(NEW));
call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficeContactAdmin(oldContact));
call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficeContactAdmin(newContact));
call revokeRoleFromRole(hsOfficeContactGuest(oldContact), hsOfficePartnerTenant(OLD));
call grantRoleToRole(hsOfficeContactGuest(newContact), hsOfficePartnerTenant(NEW));
end if;
else
raise exception 'invalid usage of TRIGGER';
end if;
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
end; $$; end; $$;
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new customer. AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_partner row.
*/ */
create trigger createRbacRolesForHsOfficePartner_Trigger
after insert
on hs_office_partner
for each row
execute procedure hsOfficePartnerRbacRolesTrigger();
/* create or replace function insertTriggerForHsOfficePartner_tf()
An AFTER UPDATE TRIGGER which updates the role structure of a customer. returns trigger
*/ language plpgsql
create trigger updateRbacRolesForHsOfficePartner_Trigger strict as $$
after update begin
on hs_office_partner call buildRbacSystemForHsOfficePartner(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficePartner_tg
after insert on hs_office_partner
for each row for each row
execute procedure hsOfficePartnerRbacRolesTrigger(); execute procedure insertTriggerForHsOfficePartner_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-partner-rbac-update-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_partner', $idName$
partnerNumber || ':' || /*
(select idName from hs_office_person_iv p where p.uuid = target.personuuid) Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|| '-' || */
(select idName from hs_office_contact_iv c where c.uuid = target.contactuuid)
$idName$); create or replace procedure updateRbacRulesForHsOfficePartner(
OLD hs_office_partner,
NEW hs_office_partner
)
language plpgsql as $$
declare
oldPartnerRel hs_office_relation;
newPartnerRel hs_office_relation;
oldPartnerDetails hs_office_partner_details;
newPartnerDetails hs_office_partner_details;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_relation WHERE uuid = OLD.partnerRelUuid INTO oldPartnerRel;
assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s', OLD.partnerRelUuid);
SELECT * FROM hs_office_relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
SELECT * FROM hs_office_partner_details WHERE uuid = OLD.detailsUuid INTO oldPartnerDetails;
assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s', OLD.detailsUuid);
SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
if NEW.partnerRelUuid <> OLD.partnerRelUuid then
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationAdmin(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationTenant(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(newPartnerRel));
end if;
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_partner row.
*/
create or replace function updateTriggerForHsOfficePartner_tf()
returns trigger
language plpgsql
strict as $$
begin
call updateRbacRulesForHsOfficePartner(OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerForHsOfficePartner_tg
after update on hs_office_partner
for each row
execute procedure updateTriggerForHsOfficePartner_tf();
--// --//
-- ============================================================================
--changeset hs-office-partner-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_partner permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
begin
call defineContext('create INSERT INTO hs_office_partner permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_partner'),
globalAdmin());
END LOOP;
END;
$$;
/**
Adds hs_office_partner INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_partner_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_partner'),
globalAdmin());
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_office_partner_global_insert_tg
after insert on global
for each row
execute procedure hs_office_partner_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_partner,
where only global-admin has that permission.
*/
create or replace function hs_office_partner_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_partner not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_partner_insert_permission_check_tg
before insert on hs_office_partner
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_partner_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_partner',
$idName$
'P-' || partnerNumber
$idName$);
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-partner-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_partner', call generateRbacRestrictedView('hs_office_partner',
'(select idName from hs_office_person_iv p where p.uuid = target.personUuid)', $orderBy$
'P-' || partnerNumber
$orderBy$,
$updates$ $updates$
partnerRelUuid = new.partnerRelUuid, partnerRelUuid = new.partnerRelUuid
personUuid = new.personUuid,
contactUuid = new.contactUuid
$updates$); $updates$);
--// --//
-- ============================================================================
--changeset hs-office-partner-rbac-NEW-PARTNER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-partner and assigns it to the Hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-partner permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-partner']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficePartnerNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-partner not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_partner_insert_trigger
before insert
on hs_office_partner
for each row
-- TODO.spec: who is allowed to create new partners
when ( not hasAssumedRole() )
execute procedure addHsOfficePartnerNotAllowedForCurrentSubjects();
--//

View File

@ -1,136 +0,0 @@
### rbac partnerDetails
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partnerDetails["`**partnerDetails**`"]
direction TB
style partnerDetails fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph partnerDetails:permissions[ ]
style partnerDetails:permissions fill:#dd4901,stroke:white
perm:partnerDetails:INSERT{{partnerDetails:INSERT}}
end
subgraph partnerRel["`**partnerRel**`"]
direction TB
style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph partnerRel:roles[ ]
style partnerRel:roles fill:#99bcdb,stroke:white
role:partnerRel:owner[[partnerRel:owner]]
role:partnerRel:admin[[partnerRel:admin]]
role:partnerRel:agent[[partnerRel:agent]]
role:partnerRel:tenant[[partnerRel:tenant]]
end
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
%% granting roles to roles
role:global:admin -.-> role:partnerRel.anchorPerson:owner
role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer
role:global:admin -.-> role:partnerRel.holderPerson:owner
role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin
role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer
role:global:admin -.-> role:partnerRel.contact:owner
role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin
role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer
role:global:admin -.-> role:partnerRel:owner
role:partnerRel:owner -.-> role:partnerRel:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin
role:partnerRel:admin -.-> role:partnerRel:agent
role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent
role:partnerRel:agent -.-> role:partnerRel:tenant
role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant
role:partnerRel.contact:admin -.-> role:partnerRel:tenant
role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.contact:referrer
%% granting permissions to roles
role:global:admin ==> perm:partnerDetails:INSERT
```

View File

@ -1,164 +0,0 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-office-partner-details-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_partner_details');
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficePartnerDetails', 'hs_office_partner_details');
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficePartnerDetails(
NEW hs_office_partner_details
)
language plpgsql as $$
declare
newPartnerRel hs_office_relation;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT partnerRel.*
FROM hs_office_relation AS partnerRel
JOIN hs_office_partner AS partner
ON partner.detailsUuid = NEW.uuid
WHERE partnerRel.uuid = partner.partnerRelUuid
INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_partner_details row.
*/
create or replace function insertTriggerForHsOfficePartnerDetails_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficePartnerDetails(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficePartnerDetails_tg
after insert on hs_office_partner_details
for each row
execute procedure insertTriggerForHsOfficePartnerDetails_tf();
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_partner_details permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO hs_office_partner_details permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
roleUuid := findRoleId(globalAdmin());
permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_partner_details');
call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP;
END;
$$;
/**
Adds hs_office_partner_details INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_partner_details_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_partner_details'),
globalAdmin());
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_office_partner_details_global_insert_tg
after insert on global
for each row
execute procedure hs_office_partner_details_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_partner_details,
where only global-admin has that permission.
*/
create or replace function hs_office_partner_details_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_partner_details not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_partner_details_insert_permission_check_tg
before insert on hs_office_partner_details
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_partner_details_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromQuery('hs_office_partner_details',
$idName$
SELECT partner_iv.idName || '-details'
FROM hs_office_partner_details AS partnerDetails
JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid
JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid
$idName$);
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_partner_details',
$orderBy$
SELECT partner_iv.idName || '-details'
FROM hs_office_partner_details AS partnerDetails
JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid
JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid
$orderBy$,
$updates$
registrationOffice = new.registrationOffice,
registrationNumber = new.registrationNumber,
birthPlace = new.birthPlace,
birthName = new.birthName,
birthday = new.birthday,
dateOfDeath = new.dateOfDeath
$updates$);
--//

View File

@ -0,0 +1,23 @@
### rbac partnerDetails
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph partnerDetails["`**partnerDetails**`"]
direction TB
style partnerDetails fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph partnerDetails:permissions[ ]
style partnerDetails:permissions fill:#dd4901,stroke:white
perm:partnerDetails:INSERT{{partnerDetails:INSERT}}
end
end
%% granting permissions to roles
role:global:admin ==> perm:partnerDetails:INSERT
```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-details-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-partner-details-rbac-OBJECT:1 endDelimiter:--//
@ -8,76 +10,141 @@ call generateRelatedRbacObject('hs_office_partner_details');
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-details-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-partner-details-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_partner_details', $idName$ call generateRbacRoleDescriptors('hsOfficePartnerDetails', 'hs_office_partner_details');
(select idName || '-details' from hs_office_partner_iv partner_iv
join hs_office_partner partner on (partner_iv.uuid = partner.uuid)
where partner.detailsUuid = target.uuid)
$idName$);
--// --//
-- ============================================================================
--changeset hs-office-partner-details-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficePartnerDetails(
NEW hs_office_partner_details
)
language plpgsql as $$
declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_partner_details row.
*/
create or replace function insertTriggerForHsOfficePartnerDetails_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficePartnerDetails(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficePartnerDetails_tg
after insert on hs_office_partner_details
for each row
execute procedure insertTriggerForHsOfficePartnerDetails_tf();
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_partner_details permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
begin
call defineContext('create INSERT INTO hs_office_partner_details permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_partner_details'),
globalAdmin());
END LOOP;
END;
$$;
/**
Adds hs_office_partner_details INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_partner_details_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_partner_details'),
globalAdmin());
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_office_partner_details_global_insert_tg
after insert on global
for each row
execute procedure hs_office_partner_details_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_partner_details,
where only global-admin has that permission.
*/
create or replace function hs_office_partner_details_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_partner_details not allowed for current subjects % (%) assumed by user % (%)',
currentSubjects(), currentSubjectsUuids(), currentUser(), currentUserUuid();
end; $$;
create trigger hs_office_partner_details_insert_permission_check_tg
before insert on hs_office_partner_details
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_partner_details_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromQuery('hs_office_partner_details',
$idName$
SELECT partnerDetails.uuid as uuid, partner_iv.idName || '-details' as idName
FROM hs_office_partner_details AS partnerDetails
JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid
JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid
$idName$);
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-details-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-partner-details-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_partner_details', call generateRbacRestrictedView('hs_office_partner_details',
'target.uuid', -- no specific order required $orderBy$
uuid
$orderBy$,
$updates$ $updates$
registrationOffice = new.registrationOffice, registrationOffice = new.registrationOffice,
registrationNumber = new.registrationNumber, registrationNumber = new.registrationNumber,
birthPlace = new.birthPlace, birthPlace = new.birthPlace,
birthName = new.birthName, birthName = new.birthName,
birthday = new.birthday, birthday = new.birthday,
dateOfDeath = new.dateOfDeath dateOfDeath = new.dateOfDeath
$updates$); $updates$);
--// --//
-- ============================================================================
--changeset hs-office-partner-details-rbac-NEW-PARTNER-DETAILS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-partner-details and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-partner-details permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-partner-details']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
-- TODO.refa: the code below could be moved to a generator, maybe even the code above.
-- Additionally, the code below is not neccesary for all entities, specifiy when it is!
/**
Used by the trigger to prevent the add-partner-details to current user respectively assumed roles.
*/
create or replace function addHsOfficePartnerDetailsNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-partner-details not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create new partner-details.
*/
create trigger hs_office_partner_details_insert_trigger
before insert
on hs_office_partner_details
for each row
when ( not hasAssumedRole() )
execute procedure addHsOfficePartnerDetailsNotAllowedForCurrentSubjects();
--//

View File

@ -9,18 +9,17 @@
Creates a single partner test record. Creates a single partner test record.
*/ */
create or replace procedure createHsOfficePartnerTestData( create or replace procedure createHsOfficePartnerTestData(
mandantTradeName varchar, mandantTradeName varchar,
partnerNumber numeric(5), newPartnerNumber numeric(5),
partnerPersonName varchar, partnerPersonName varchar,
contactLabel varchar ) contactLabel varchar )
language plpgsql as $$ language plpgsql as $$
declare declare
currentTask varchar; currentTask varchar;
idName varchar; idName varchar;
mandantPerson hs_office_person; mandantPerson hs_office_person;
partnerRel hs_office_relation; partnerRel hs_office_relation;
relatedPerson hs_office_person; relatedPerson hs_office_person;
relatedContact hs_office_contact;
relatedDetailsUuid uuid; relatedDetailsUuid uuid;
begin begin
idName := cleanIdentifier( partnerPersonName|| '-' || contactLabel); idName := cleanIdentifier( partnerPersonName|| '-' || contactLabel);
@ -38,9 +37,6 @@ begin
select p.* from hs_office_person p select p.* from hs_office_person p
where p.tradeName = partnerPersonName or p.familyName = partnerPersonName where p.tradeName = partnerPersonName or p.familyName = partnerPersonName
into relatedPerson; into relatedPerson;
select c.* from hs_office_contact c
where c.label = contactLabel
into relatedContact;
select r.* from hs_office_relation r select r.* from hs_office_relation r
where r.type = 'PARTNER' where r.type = 'PARTNER'
@ -53,7 +49,6 @@ begin
raise notice 'creating test partner: %', idName; raise notice 'creating test partner: %', idName;
raise notice '- using partnerRel (%): %', partnerRel.uuid, partnerRel; raise notice '- using partnerRel (%): %', partnerRel.uuid, partnerRel;
raise notice '- using person (%): %', relatedPerson.uuid, relatedPerson; raise notice '- using person (%): %', relatedPerson.uuid, relatedPerson;
raise notice '- using contact (%): %', relatedContact.uuid, relatedContact;
if relatedPerson.persontype = 'NP' then if relatedPerson.persontype = 'NP' then
insert insert
@ -68,8 +63,8 @@ begin
end if; end if;
insert insert
into hs_office_partner (uuid, partnerNumber, partnerRelUuid, personuuid, contactuuid, detailsUuid) into hs_office_partner (uuid, partnerNumber, partnerRelUuid, detailsUuid)
values (uuid_generate_v4(), partnerNumber, partnerRel.uuid, relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid); values (uuid_generate_v4(), newPartnerNumber, partnerRel.uuid, relatedDetailsUuid);
end; $$; end; $$;
--// --//

View File

@ -1,43 +0,0 @@
### rbac bankAccount
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph bankAccount["`**bankAccount**`"]
direction TB
style bankAccount fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph bankAccount:roles[ ]
style bankAccount:roles fill:#dd4901,stroke:white
role:bankAccount:owner[[bankAccount:owner]]
role:bankAccount:admin[[bankAccount:admin]]
role:bankAccount:referrer[[bankAccount:referrer]]
end
subgraph bankAccount:permissions[ ]
style bankAccount:permissions fill:#dd4901,stroke:white
perm:bankAccount:DELETE{{bankAccount:DELETE}}
perm:bankAccount:UPDATE{{bankAccount:UPDATE}}
perm:bankAccount:SELECT{{bankAccount:SELECT}}
end
end
%% granting roles to users
user:creator ==> role:bankAccount:owner
%% granting roles to roles
role:global:admin ==> role:bankAccount:owner
role:bankAccount:owner ==> role:bankAccount:admin
role:bankAccount:admin ==> role:bankAccount:referrer
%% granting permissions to roles
role:bankAccount:owner ==> perm:bankAccount:DELETE
role:bankAccount:admin ==> perm:bankAccount:UPDATE
role:bankAccount:referrer ==> perm:bankAccount:SELECT
```

View File

@ -1,125 +0,0 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-office-bankaccount-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_bankaccount');
--//
-- ============================================================================
--changeset hs-office-bankaccount-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficeBankAccount', 'hs_office_bankaccount');
--//
-- ============================================================================
--changeset hs-office-bankaccount-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficeBankAccount(
NEW hs_office_bankaccount
)
language plpgsql as $$
declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
perform createRoleWithGrants(
hsOfficeBankAccountOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficeBankAccountAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeBankAccountOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeBankAccountReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeBankAccountAdmin(NEW)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_bankaccount row.
*/
create or replace function insertTriggerForHsOfficeBankAccount_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeBankAccount(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeBankAccount_tg
after insert on hs_office_bankaccount
for each row
execute procedure insertTriggerForHsOfficeBankAccount_tf();
--//
-- ============================================================================
--changeset hs-office-bankaccount-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_bankaccount,
where only global-admin has that permission.
*/
create or replace function hs_office_bankaccount_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_bankaccount not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_bankaccount_insert_permission_check_tg
before insert on hs_office_bankaccount
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_bankaccount_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-bankaccount-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_bankaccount',
$idName$
iban || ':' || holder
$idName$);
--//
-- ============================================================================
--changeset hs-office-bankaccount-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_bankaccount',
$orderBy$
iban || ':' || holder
$orderBy$,
$updates$
holder = new.holder,
iban = new.iban,
bic = new.bic
$updates$);
--//

View File

@ -1,40 +1,45 @@
### hs_office_bankaccount RBAC Roles ### rbac bankAccount
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
subgraph global subgraph bankAccount["`**bankAccount**`"]
style global fill: lightgray
role:global.admin[global.admin]
end
subgraph hsOfficeBankAccount
direction TB direction TB
style hsOfficeBankAccount fill: lightgreen style bankAccount fill:#dd4901,stroke:#274d6e,stroke-width:8px
user:hsOfficeBankAccount.creator([bankAccount.creator])
role:hsOfficeBankAccount.owner[[bankAccount.owner]] subgraph bankAccount:roles[ ]
%% permissions style bankAccount:roles fill:#dd4901,stroke:white
role:hsOfficeBankAccount.owner --> perm:hsOfficeBankAccount.*{{hsOfficeBankAccount.delete}}
%% incoming role:bankAccount:owner[[bankAccount:owner]]
role:global.admin --> role:hsOfficeBankAccount.owner role:bankAccount:admin[[bankAccount:admin]]
user:hsOfficeBankAccount.creator ---> role:hsOfficeBankAccount.owner role:bankAccount:referrer[[bankAccount:referrer]]
end
role:hsOfficeBankAccount.admin[[bankAccount.admin]]
%% incoming subgraph bankAccount:permissions[ ]
role:hsOfficeBankAccount.owner ---> role:hsOfficeBankAccount.admin style bankAccount:permissions fill:#dd4901,stroke:white
role:hsOfficeBankAccount.tenant[[bankAccount.tenant]] perm:bankAccount:INSERT{{bankAccount:INSERT}}
%% incoming perm:bankAccount:DELETE{{bankAccount:DELETE}}
role:hsOfficeBankAccount.admin ---> role:hsOfficeBankAccount.tenant perm:bankAccount:UPDATE{{bankAccount:UPDATE}}
perm:bankAccount:SELECT{{bankAccount:SELECT}}
role:hsOfficeBankAccount.guest[[bankAccount.guest]] end
%% permissions
role:hsOfficeBankAccount.guest --> perm:hsOfficeBankAccount.view{{hsOfficeBankAccount.view}}
%% incoming
role:hsOfficeBankAccount.tenant ---> role:hsOfficeBankAccount.guest
end end
```
%% granting roles to users
user:creator ==> role:bankAccount:owner
%% granting roles to roles
role:global:admin ==> role:bankAccount:owner
role:bankAccount:owner ==> role:bankAccount:admin
role:bankAccount:admin ==> role:bankAccount:referrer
%% granting permissions to roles
role:global:guest ==> perm:bankAccount:INSERT
role:bankAccount:owner ==> perm:bankAccount:DELETE
role:bankAccount:admin ==> perm:bankAccount:UPDATE
role:bankAccount:referrer ==> perm:bankAccount:SELECT
```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-bankaccount-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-bankaccount-rbac-OBJECT:1 endDelimiter:--//
@ -15,125 +17,129 @@ call generateRbacRoleDescriptors('hsOfficeBankAccount', 'hs_office_bankaccount')
-- ============================================================================ -- ============================================================================
--changeset hs-office-bankaccount-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-bankaccount-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates the roles and their assignments for a new bankaccount for the AFTER INSERT TRIGGER. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function createRbacRolesForHsOfficeBankAccount() create or replace procedure buildRbacSystemForHsOfficeBankAccount(
NEW hs_office_bankaccount
)
language plpgsql as $$
declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
perform createRoleWithGrants(
hsOfficeBankAccountOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficeBankAccountAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeBankAccountOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeBankAccountReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeBankAccountAdmin(NEW)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_bankaccount row.
*/
create or replace function insertTriggerForHsOfficeBankAccount_tf()
returns trigger returns trigger
language plpgsql language plpgsql
strict as $$ strict as $$
begin begin
if TG_OP <> 'INSERT' then call buildRbacSystemForHsOfficeBankAccount(NEW);
raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if;
perform createRoleWithGrants(
hsOfficeBankAccountOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()],
grantedByRole => globalAdmin()
);
perform createRoleWithGrants(
hsOfficeBankAccountAdmin(NEW),
incomingSuperRoles => array[hsOfficeBankAccountOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeBankAccountTenant(NEW),
incomingSuperRoles => array[hsOfficeBankAccountAdmin(NEW)]
);
perform createRoleWithGrants(
hsOfficeBankAccountGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeBankAccountTenant(NEW)]
);
return NEW; return NEW;
end; $$; end; $$;
/* create trigger insertTriggerForHsOfficeBankAccount_tg
An AFTER INSERT TRIGGER which creates the role structure for a new customer. after insert on hs_office_bankaccount
*/
create trigger createRbacRolesForHsOfficeBankAccount_Trigger
after insert
on hs_office_bankaccount
for each row for each row
execute procedure createRbacRolesForHsOfficeBankAccount(); execute procedure insertTriggerForHsOfficeBankAccount_tf();
--// --//
-- ============================================================================
--changeset hs-office-bankaccount-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_bankaccount permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
begin
call defineContext('create INSERT INTO hs_office_bankaccount permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_bankaccount'),
globalGuest());
END LOOP;
END;
$$;
/**
Adds hs_office_bankaccount INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_bankaccount_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_bankaccount'),
globalGuest());
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_office_bankaccount_global_insert_tg
after insert on global
for each row
execute procedure hs_office_bankaccount_global_insert_tf();
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-bankaccount-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-bankaccount-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_bankaccount', $idName$ call generateRbacIdentityViewFromProjection('hs_office_bankaccount',
target.holder $idName$
iban
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-bankaccount-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-bankaccount-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_bankaccount', 'target.holder', call generateRbacRestrictedView('hs_office_bankaccount',
$orderBy$
iban
$orderBy$,
$updates$ $updates$
holder = new.holder, holder = new.holder,
iban = new.iban, iban = new.iban,
bic = new.bic bic = new.bic
$updates$); $updates$);
--/
-- ============================================================================
--changeset hs-office-bankaccount-rbac-NEW-BANKACCOUNT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-bankaccount and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-bankaccount permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-bankaccount']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeBankAccountNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-bankaccount not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_bankaccount_insert_trigger
before insert
on hs_office_bankaccount
for each row
-- TODO.spec: who is allowed to create new bankaccounts
when ( not hasAssumedRole() )
execute procedure addHsOfficeBankAccountNotAllowedForCurrentSubjects();
--// --//

View File

@ -1,178 +0,0 @@
### rbac sepaMandate
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph bankAccount["`**bankAccount**`"]
direction TB
style bankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph bankAccount:roles[ ]
style bankAccount:roles fill:#99bcdb,stroke:white
role:bankAccount:owner[[bankAccount:owner]]
role:bankAccount:admin[[bankAccount:admin]]
role:bankAccount:referrer[[bankAccount:referrer]]
end
end
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact:roles[ ]
style debitorRel.contact:roles fill:#99bcdb,stroke:white
role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson:roles[ ]
style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph sepaMandate["`**sepaMandate**`"]
direction TB
style sepaMandate fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph sepaMandate:roles[ ]
style sepaMandate:roles fill:#dd4901,stroke:white
role:sepaMandate:owner[[sepaMandate:owner]]
role:sepaMandate:admin[[sepaMandate:admin]]
role:sepaMandate:agent[[sepaMandate:agent]]
role:sepaMandate:referrer[[sepaMandate:referrer]]
end
subgraph sepaMandate:permissions[ ]
style sepaMandate:permissions fill:#dd4901,stroke:white
perm:sepaMandate:DELETE{{sepaMandate:DELETE}}
perm:sepaMandate:UPDATE{{sepaMandate:UPDATE}}
perm:sepaMandate:SELECT{{sepaMandate:SELECT}}
end
end
subgraph debitorRel["`**debitorRel**`"]
direction TB
style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact:roles[ ]
style debitorRel.contact:roles fill:#99bcdb,stroke:white
role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson:roles[ ]
style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph debitorRel:roles[ ]
style debitorRel:roles fill:#99bcdb,stroke:white
role:debitorRel:owner[[debitorRel:owner]]
role:debitorRel:admin[[debitorRel:admin]]
role:debitorRel:agent[[debitorRel:agent]]
role:debitorRel:tenant[[debitorRel:tenant]]
end
end
%% granting roles to users
user:creator ==> role:sepaMandate:owner
%% granting roles to roles
role:global:admin -.-> role:debitorRel.anchorPerson:owner
role:debitorRel.anchorPerson:owner -.-> role:debitorRel.anchorPerson:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel.anchorPerson:referrer
role:global:admin -.-> role:debitorRel.holderPerson:owner
role:debitorRel.holderPerson:owner -.-> role:debitorRel.holderPerson:admin
role:debitorRel.holderPerson:admin -.-> role:debitorRel.holderPerson:referrer
role:global:admin -.-> role:debitorRel.contact:owner
role:debitorRel.contact:owner -.-> role:debitorRel.contact:admin
role:debitorRel.contact:admin -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:debitorRel:owner
role:debitorRel:owner -.-> role:debitorRel:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel:admin
role:debitorRel:admin -.-> role:debitorRel:agent
role:debitorRel.holderPerson:admin -.-> role:debitorRel:agent
role:debitorRel:agent -.-> role:debitorRel:tenant
role:debitorRel.holderPerson:admin -.-> role:debitorRel:tenant
role:debitorRel.contact:admin -.-> role:debitorRel:tenant
role:debitorRel:tenant -.-> role:debitorRel.anchorPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.holderPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:bankAccount:owner
role:bankAccount:owner -.-> role:bankAccount:admin
role:bankAccount:admin -.-> role:bankAccount:referrer
role:global:admin ==> role:sepaMandate:owner
role:sepaMandate:owner ==> role:sepaMandate:admin
role:sepaMandate:admin ==> role:sepaMandate:agent
role:sepaMandate:agent ==> role:bankAccount:referrer
role:sepaMandate:agent ==> role:debitorRel:agent
role:sepaMandate:agent ==> role:sepaMandate:referrer
role:bankAccount:admin ==> role:sepaMandate:referrer
role:debitorRel:agent ==> role:sepaMandate:referrer
role:sepaMandate:referrer ==> role:debitorRel:tenant
%% granting permissions to roles
role:sepaMandate:owner ==> perm:sepaMandate:DELETE
role:sepaMandate:admin ==> perm:sepaMandate:UPDATE
role:sepaMandate:referrer ==> perm:sepaMandate:SELECT
```

View File

@ -1,143 +0,0 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-office-sepamandate-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_sepamandate');
--//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficeSepaMandate', 'hs_office_sepamandate');
--//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficeSepaMandate(
NEW hs_office_sepamandate
)
language plpgsql as $$
declare
newBankAccount hs_office_bankaccount;
newDebitorRel hs_office_relation;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_bankaccount WHERE uuid = NEW.bankAccountUuid INTO newBankAccount;
SELECT * FROM hs_office_relation WHERE uuid = NEW.debitorRelUuid INTO newDebitorRel;
perform createRoleWithGrants(
hsOfficeSepaMandateOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficeSepaMandateAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeSepaMandateOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeSepaMandateAgent(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)],
outgoingSubRoles => array[
hsOfficeBankAccountReferrer(newBankAccount),
hsOfficeRelationAgent(newDebitorRel)]
);
perform createRoleWithGrants(
hsOfficeSepaMandateReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeBankAccountAdmin(newBankAccount),
hsOfficeRelationAgent(newDebitorRel),
hsOfficeSepaMandateAgent(NEW)],
outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_sepamandate row.
*/
create or replace function insertTriggerForHsOfficeSepaMandate_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeSepaMandate(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeSepaMandate_tg
after insert on hs_office_sepamandate
for each row
execute procedure insertTriggerForHsOfficeSepaMandate_tf();
--//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_sepamandate,
where only global-admin has that permission.
*/
create or replace function hs_office_sepamandate_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_sepamandate not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_sepamandate_insert_permission_check_tg
before insert on hs_office_sepamandate
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_sepamandate_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_sepamandate',
$idName$
concat(tradeName, familyName, givenName)
$idName$);
--//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_sepamandate',
$orderBy$
concat(tradeName, familyName, givenName)
$orderBy$,
$updates$
reference = new.reference,
agreement = new.agreement,
validity = new.validity
$updates$);
--//

View File

@ -1,71 +1,180 @@
### hs_office_sepaMandate RBAC ### rbac sepaMandate
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
subgraph global subgraph bankAccount["`**bankAccount**`"]
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeBankAccount
direction TB direction TB
style hsOfficeBankAccount fill:#eee style bankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeBankAccount.owner[bankAccount.owner] subgraph bankAccount:roles[ ]
--> role:hsOfficeBankAccount.admin[bankAccount.admin] style bankAccount:roles fill:#99bcdb,stroke:white
--> role:hsOfficeBankAccount.tenant[bankAccount.tenant]
--> role:hsOfficeBankAccount.guest[bankAccount.guest] role:bankAccount:owner[[bankAccount:owner]]
role:bankAccount:admin[[bankAccount:admin]]
role:bankAccount:referrer[[bankAccount:referrer]]
end
end end
subgraph hsOfficeDebitor subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB direction TB
style hsOfficeDebitor fill:#eee style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeDebitor.owner[debitor.admin] subgraph debitorRel.contact:roles[ ]
--> role:hsOfficeDebitor.admin[debitor.admin] style debitorRel.contact:roles fill:#99bcdb,stroke:white
--> role:hsOfficeDebitor.agent[debitor.agent]
--> role:hsOfficeDebitor.tenant[debitor.tenant] role:debitorRel.contact:owner[[debitorRel.contact:owner]]
--> role:hsOfficeDebitor.guest[debitor.guest] role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end end
subgraph hsOfficeSepaMandate subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
role:hsOfficeSepaMandate.owner[sepaMandate.owner] style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
%% permissions
role:hsOfficeSepaMandate.owner --> perm:hsOfficeSepaMandate.*{{sepaMandate.*}}
%% incoming
role:global.admin ---> role:hsOfficeSepaMandate.owner
role:hsOfficeSepaMandate.admin[sepaMandate.admin]
%% permissions
role:hsOfficeSepaMandate.admin --> perm:hsOfficeSepaMandate.edit{{sepaMandate.edit}}
%% incoming
role:hsOfficeSepaMandate.owner ---> role:hsOfficeSepaMandate.admin
role:hsOfficeSepaMandate.agent[sepaMandate.agent]
%% incoming
role:hsOfficeSepaMandate.admin ---> role:hsOfficeSepaMandate.agent
role:hsOfficeDebitor.admin --> role:hsOfficeSepaMandate.agent
role:hsOfficeBankAccount.admin --> role:hsOfficeSepaMandate.agent
%% outgoing
role:hsOfficeSepaMandate.agent --> role:hsOfficeDebitor.tenant
role:hsOfficeSepaMandate.admin --> role:hsOfficeBankAccount.tenant
role:hsOfficeSepaMandate.tenant[sepaMandate.tenant]
%% incoming
role:hsOfficeSepaMandate.agent --> role:hsOfficeSepaMandate.tenant
%% outgoing
role:hsOfficeSepaMandate.tenant --> role:hsOfficeDebitor.guest
role:hsOfficeSepaMandate.tenant --> role:hsOfficeBankAccount.guest
role:hsOfficeSepaMandate.guest[sepaMandate.guest] subgraph debitorRel.anchorPerson:roles[ ]
%% permissions style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:hsOfficeSepaMandate.guest --> perm:hsOfficeSepaMandate.view{{sepaMandate.view}}
%% incoming role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:hsOfficeSepaMandate.tenant --> role:hsOfficeSepaMandate.guest role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph sepaMandate["`**sepaMandate**`"]
direction TB
style sepaMandate fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph sepaMandate:roles[ ]
style sepaMandate:roles fill:#dd4901,stroke:white
role:sepaMandate:owner[[sepaMandate:owner]]
role:sepaMandate:admin[[sepaMandate:admin]]
role:sepaMandate:agent[[sepaMandate:agent]]
role:sepaMandate:referrer[[sepaMandate:referrer]]
end
subgraph sepaMandate:permissions[ ]
style sepaMandate:permissions fill:#dd4901,stroke:white
perm:sepaMandate:DELETE{{sepaMandate:DELETE}}
perm:sepaMandate:UPDATE{{sepaMandate:UPDATE}}
perm:sepaMandate:SELECT{{sepaMandate:SELECT}}
perm:sepaMandate:INSERT{{sepaMandate:INSERT}}
end
end
subgraph debitorRel["`**debitorRel**`"]
direction TB
style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact:roles[ ]
style debitorRel.contact:roles fill:#99bcdb,stroke:white
role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson:roles[ ]
style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph debitorRel:roles[ ]
style debitorRel:roles fill:#99bcdb,stroke:white
role:debitorRel:owner[[debitorRel:owner]]
role:debitorRel:admin[[debitorRel:admin]]
role:debitorRel:agent[[debitorRel:agent]]
role:debitorRel:tenant[[debitorRel:tenant]]
end
end
%% granting roles to users
user:creator ==> role:sepaMandate:owner
%% granting roles to roles
role:global:admin -.-> role:debitorRel.anchorPerson:owner
role:debitorRel.anchorPerson:owner -.-> role:debitorRel.anchorPerson:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel.anchorPerson:referrer
role:global:admin -.-> role:debitorRel.holderPerson:owner
role:debitorRel.holderPerson:owner -.-> role:debitorRel.holderPerson:admin
role:debitorRel.holderPerson:admin -.-> role:debitorRel.holderPerson:referrer
role:global:admin -.-> role:debitorRel.contact:owner
role:debitorRel.contact:owner -.-> role:debitorRel.contact:admin
role:debitorRel.contact:admin -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:debitorRel:owner
role:debitorRel:owner -.-> role:debitorRel:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel:admin
role:debitorRel:admin -.-> role:debitorRel:agent
role:debitorRel.holderPerson:admin -.-> role:debitorRel:agent
role:debitorRel:agent -.-> role:debitorRel:tenant
role:debitorRel.holderPerson:admin -.-> role:debitorRel:tenant
role:debitorRel.contact:admin -.-> role:debitorRel:tenant
role:debitorRel:tenant -.-> role:debitorRel.anchorPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.holderPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:bankAccount:owner
role:bankAccount:owner -.-> role:bankAccount:admin
role:bankAccount:admin -.-> role:bankAccount:referrer
role:global:admin ==> role:sepaMandate:owner
role:sepaMandate:owner ==> role:sepaMandate:admin
role:sepaMandate:admin ==> role:sepaMandate:agent
role:sepaMandate:agent ==> role:bankAccount:referrer
role:sepaMandate:agent ==> role:debitorRel:agent
role:sepaMandate:agent ==> role:sepaMandate:referrer
role:bankAccount:admin ==> role:sepaMandate:referrer
role:debitorRel:agent ==> role:sepaMandate:referrer
role:sepaMandate:referrer ==> role:debitorRel:tenant
%% granting permissions to roles
role:sepaMandate:owner ==> perm:sepaMandate:DELETE
role:sepaMandate:admin ==> perm:sepaMandate:UPDATE
role:sepaMandate:referrer ==> perm:sepaMandate:SELECT
role:debitorRel:admin ==> perm:sepaMandate:INSERT
``` ```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-sepamandate-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-sepamandate-rbac-OBJECT:1 endDelimiter:--//
@ -15,144 +17,191 @@ call generateRbacRoleDescriptors('hsOfficeSepaMandate', 'hs_office_sepamandate')
-- ============================================================================ -- ============================================================================
--changeset hs-office-sepamandate-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-sepamandate-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates and updates the roles and their assignments for sepaMandate entities. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function hsOfficeSepaMandateRbacRolesTrigger() create or replace procedure buildRbacSystemForHsOfficeSepaMandate(
returns trigger NEW hs_office_sepamandate
language plpgsql )
strict as $$ language plpgsql as $$
declare declare
newHsOfficeDebitor hs_office_debitor; newBankAccount hs_office_bankaccount;
newHsOfficeBankAccount hs_office_bankAccount; newDebitorRel hs_office_relation;
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_debitor as p where p.uuid = NEW.debitorUuid into newHsOfficeDebitor; SELECT * FROM hs_office_bankaccount WHERE uuid = NEW.bankAccountUuid INTO newBankAccount;
select * from hs_office_bankAccount as c where c.uuid = NEW.bankAccountUuid into newHsOfficeBankAccount; assert newBankAccount.uuid is not null, format('newBankAccount must not be null for NEW.bankAccountUuid = %s', NEW.bankAccountUuid);
if TG_OP = 'INSERT' then SELECT debitorRel.*
FROM hs_office_relation debitorRel
JOIN hs_office_debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
WHERE debitor.uuid = NEW.debitorUuid
INTO newDebitorRel;
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s', NEW.debitorUuid);
-- === ATTENTION: code generated from related Mermaid flowchart: ===
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateOwner(NEW), hsOfficeSepaMandateOwner(NEW),
permissions => array['DELETE'], permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()] incomingSuperRoles => array[globalAdmin()],
); userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateAdmin(NEW), hsOfficeSepaMandateAdmin(NEW),
permissions => array['UPDATE'], permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeSepaMandateOwner(NEW)], incomingSuperRoles => array[hsOfficeSepaMandateOwner(NEW)]
outgoingSubRoles => array[hsOfficeBankAccountTenant(newHsOfficeBankAccount)] );
);
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateAgent(NEW), hsOfficeSepaMandateAgent(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW), hsOfficeDebitorAdmin(newHsOfficeDebitor), hsOfficeBankAccountAdmin(newHsOfficeBankAccount)], incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)],
outgoingSubRoles => array[hsOfficeDebitorTenant(newHsOfficeDebitor)] outgoingSubRoles => array[
); hsOfficeBankAccountReferrer(newBankAccount),
hsOfficeRelationAgent(newDebitorRel)]
);
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeSepaMandateTenant(NEW), hsOfficeSepaMandateReferrer(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAgent(NEW)], permissions => array['SELECT'],
outgoingSubRoles => array[hsOfficeDebitorGuest(newHsOfficeDebitor), hsOfficeBankAccountGuest(newHsOfficeBankAccount)] incomingSuperRoles => array[
); hsOfficeBankAccountAdmin(newBankAccount),
hsOfficeRelationAgent(newDebitorRel),
perform createRoleWithGrants( hsOfficeSepaMandateAgent(NEW)],
hsOfficeSepaMandateGuest(NEW), outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)]
permissions => array['SELECT'], );
incomingSuperRoles => array[hsOfficeSepaMandateTenant(NEW)]
);
-- === END of code generated from Mermaid flowchart. ===
else
raise exception 'invalid usage of TRIGGER';
end if;
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
end; $$; end; $$;
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new customer. AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_sepamandate row.
*/ */
create trigger createRbacRolesForHsOfficeSepaMandate_Trigger
after insert create or replace function insertTriggerForHsOfficeSepaMandate_tf()
on hs_office_sepamandate returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeSepaMandate(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeSepaMandate_tg
after insert on hs_office_sepamandate
for each row for each row
execute procedure hsOfficeSepaMandateRbacRolesTrigger(); execute procedure insertTriggerForHsOfficeSepaMandate_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-sepamandate-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-sepamandate-rbac-INSERT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_sepamandate', 'target.reference');
/*
Creates INSERT INTO hs_office_sepamandate permissions for the related hs_office_relation rows.
*/
do language plpgsql $$
declare
row hs_office_relation;
begin
call defineContext('create INSERT INTO hs_office_sepamandate permissions for the related hs_office_relation rows');
FOR row IN SELECT * FROM hs_office_relation
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_sepamandate'),
hsOfficeRelationAdmin(row));
END LOOP;
END;
$$;
/**
Adds hs_office_sepamandate INSERT permission to specified role of new hs_office_relation rows.
*/
create or replace function hs_office_sepamandate_hs_office_relation_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_sepamandate'),
hsOfficeRelationAdmin(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_office_sepamandate_hs_office_relation_insert_tg
after insert on hs_office_relation
for each row
execute procedure hs_office_sepamandate_hs_office_relation_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_sepamandate,
where the check is performed by an indirect role.
An indirect role is a role which depends on an object uuid which is not a direct foreign key
of the source entity, but needs to be fetched via joined tables.
*/
create or replace function hs_office_sepamandate_insert_permission_check_tf()
returns trigger
language plpgsql as $$
declare
superRoleObjectUuid uuid;
begin
superRoleObjectUuid := (SELECT debitorRel.uuid
FROM hs_office_relation debitorRel
JOIN hs_office_debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
WHERE debitor.uuid = NEW.debitorUuid
);
assert superRoleObjectUuid is not null, 'superRoleObjectUuid must not be null';
if ( not hasInsertPermission(superRoleObjectUuid, 'INSERT', 'hs_office_sepamandate') ) then
raise exception
'[403] insert into hs_office_sepamandate not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end if;
return NEW;
end; $$;
create trigger hs_office_sepamandate_insert_permission_check_tg
before insert on hs_office_sepamandate
for each row
execute procedure hs_office_sepamandate_insert_permission_check_tf();
--// --//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromQuery('hs_office_sepamandate',
$idName$
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
from hs_office_sepamandate sm
join hs_office_bankaccount ba on ba.uuid = sm.bankAccountUuid
$idName$);
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-sepamandate-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-sepamandate-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_sepamandate', call generateRbacRestrictedView('hs_office_sepamandate',
orderby => 'target.reference', $orderBy$
columnUpdates => $updates$ validity
$orderBy$,
$updates$
reference = new.reference, reference = new.reference,
agreement = new.agreement, agreement = new.agreement,
validity = new.validity validity = new.validity
$updates$); $updates$);
--// --//
-- ============================================================================
--changeset hs-office-sepamandate-rbac-NEW-SepaMandate:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-sepaMandate and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-sepaMandate permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-sepamandate']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeSepaMandateNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-sepaMandate not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_sepamandate_insert_trigger
before insert
on hs_office_sepamandate
for each row
-- TODO.spec: who is allowed to create new sepaMandates
when ( not hasAssumedRole() )
execute procedure addHsOfficeSepaMandateNotAllowedForCurrentSubjects();
--//

View File

@ -8,31 +8,36 @@
/* /*
Creates a single sepaMandate test record. Creates a single sepaMandate test record.
*/ */
create or replace procedure createHsOfficeSepaMandateTestData( tradeNameAndHolderName varchar ) create or replace procedure createHsOfficeSepaMandateTestData(
forPartnerNumber numeric(5),
forDebitorSuffix numeric(2),
forIban varchar,
withReference varchar)
language plpgsql as $$ language plpgsql as $$
declare declare
currentTask varchar; currentTask varchar;
idName varchar;
relatedDebitor hs_office_debitor; relatedDebitor hs_office_debitor;
relatedBankAccount hs_office_bankAccount; relatedBankAccount hs_office_bankAccount;
begin begin
idName := cleanIdentifier( tradeNameAndHolderName); currentTask := 'creating SEPA-mandate test-data ' || forPartnerNumber::text || forDebitorSuffix::text;
currentTask := 'creating SEPA-mandate test-data ' || idName;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin'); call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask); execute format('set local hsadminng.currentTask to %L', currentTask);
select debitor.* from hs_office_debitor debitor select debitor.* into relatedDebitor
join hs_office_partner parter on parter.uuid = debitor.partnerUuid from hs_office_debitor debitor
join hs_office_person person on person.uuid = parter.personUuid join hs_office_relation debitorRel on debitorRel.uuid = debitor.debitorRelUuid
where person.tradeName = tradeNameAndHolderName into relatedDebitor; join hs_office_relation partnerRel on partnerRel.holderUuid = debitorRel.anchorUuid
select c.* from hs_office_bankAccount c where c.holder = tradeNameAndHolderName into relatedBankAccount; join hs_office_partner partner on partner.partnerRelUuid = partnerRel.uuid
where partner.partnerNumber = forPartnerNumber and debitor.debitorNumberSuffix = forDebitorSuffix;
select b.* into relatedBankAccount
from hs_office_bankAccount b where b.iban = forIban;
raise notice 'creating test SEPA-mandate: %', idName; raise notice 'creating test SEPA-mandate: %', forPartnerNumber::text || forDebitorSuffix::text;
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor; raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
raise notice '- using bankAccount (%): %', relatedBankAccount.uuid, relatedBankAccount; raise notice '- using bankAccount (%): %', relatedBankAccount.uuid, relatedBankAccount;
insert insert
into hs_office_sepamandate (uuid, debitoruuid, bankAccountuuid, reference, agreement, validity) into hs_office_sepamandate (uuid, debitoruuid, bankAccountuuid, reference, agreement, validity)
values (uuid_generate_v4(), relatedDebitor.uuid, relatedBankAccount.uuid, 'ref'||idName, '20220930', daterange('20221001' , '20261231', '[]')); values (uuid_generate_v4(), relatedDebitor.uuid, relatedBankAccount.uuid, withReference, '20220930', daterange('20221001' , '20261231', '[]'));
end; $$; end; $$;
--// --//
@ -43,9 +48,9 @@ end; $$;
do language plpgsql $$ do language plpgsql $$
begin begin
call createHsOfficeSepaMandateTestData('First GmbH'); call createHsOfficeSepaMandateTestData(10001, 11, 'DE02120300000000202051', 'ref-10001-11');
call createHsOfficeSepaMandateTestData('Second e.K.'); call createHsOfficeSepaMandateTestData(10002, 12, 'DE02100500000054540402', 'ref-10002-12');
call createHsOfficeSepaMandateTestData('Third OHG'); call createHsOfficeSepaMandateTestData(10003, 13, 'DE02300209000106531065', 'ref-10003-13');
end; end;
$$; $$;
--// --//

View File

@ -7,10 +7,9 @@
create table hs_office_debitor create table hs_office_debitor
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
partnerUuid uuid not null references hs_office_partner(uuid),
billable boolean not null default true,
debitorNumberSuffix numeric(2) not null, debitorNumberSuffix numeric(2) not null,
billingContactUuid uuid not null references hs_office_contact(uuid), debitorRelUuid uuid not null references hs_office_relation(uuid),
billable boolean not null default true,
vatId varchar(24), -- TODO.spec: here or in person? vatId varchar(24), -- TODO.spec: here or in person?
vatCountryCode varchar(2), vatCountryCode varchar(2),
vatBusiness boolean not null, vatBusiness boolean not null,
@ -20,11 +19,43 @@ create table hs_office_debitor
constraint check_default_prefix check ( constraint check_default_prefix check (
defaultPrefix::text ~ '^([a-z]{3}|al0|bh1|c4s|f3k|k8i|l3d|mh1|o13|p2m|s80|t4w)$' defaultPrefix::text ~ '^([a-z]{3}|al0|bh1|c4s|f3k|k8i|l3d|mh1|o13|p2m|s80|t4w)$'
) )
-- TODO.impl: SEPA-mandate
); );
--// --//
-- ============================================================================
--changeset hs-office-debitor-DELETE-DEPENDENTS-TRIGGER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Trigger function to delete related rows of a debitor to delete.
*/
create or replace function deleteHsOfficeDependentsOnDebitorDelete()
returns trigger
language PLPGSQL
as $$
declare
counter integer;
begin
DELETE FROM hs_office_relation r WHERE r.uuid = OLD.debitorRelUuid;
GET DIAGNOSTICS counter = ROW_COUNT;
if counter = 0 then
raise exception 'debitor relation % could not be deleted', OLD.debitorRelUuid;
end if;
RETURN OLD;
end; $$;
/**
Triggers deletion of related details of a debitor to delete.
*/
create trigger hs_office_debitor_delete_dependents_trigger
after delete
on hs_office_debitor
for each row
execute procedure deleteHsOfficeDependentsOnDebitorDelete();
-- ============================================================================ -- ============================================================================
--changeset hs-office-debitor-MAIN-TABLE-JOURNAL:1 endDelimiter:--// --changeset hs-office-debitor-MAIN-TABLE-JOURNAL:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -1,275 +0,0 @@
### rbac debitor
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson:roles[ ]
style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph debitor["`**debitor**`"]
direction TB
style debitor fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph debitor:permissions[ ]
style debitor:permissions fill:#dd4901,stroke:white
perm:debitor:INSERT{{debitor:INSERT}}
perm:debitor:DELETE{{debitor:DELETE}}
perm:debitor:UPDATE{{debitor:UPDATE}}
perm:debitor:SELECT{{debitor:SELECT}}
end
subgraph debitorRel["`**debitorRel**`"]
direction TB
style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson:roles[ ]
style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact:roles[ ]
style debitorRel.contact:roles fill:#99bcdb,stroke:white
role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end
subgraph debitorRel:roles[ ]
style debitorRel:roles fill:#99bcdb,stroke:white
role:debitorRel:owner[[debitorRel:owner]]
role:debitorRel:admin[[debitorRel:admin]]
role:debitorRel:agent[[debitorRel:agent]]
role:debitorRel:tenant[[debitorRel:tenant]]
end
end
end
subgraph partnerRel["`**partnerRel**`"]
direction TB
style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel:roles[ ]
style partnerRel:roles fill:#99bcdb,stroke:white
role:partnerRel:owner[[partnerRel:owner]]
role:partnerRel:admin[[partnerRel:admin]]
role:partnerRel:agent[[partnerRel:agent]]
role:partnerRel:tenant[[partnerRel:tenant]]
end
end
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact:roles[ ]
style debitorRel.contact:roles fill:#99bcdb,stroke:white
role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph refundBankAccount["`**refundBankAccount**`"]
direction TB
style refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph refundBankAccount:roles[ ]
style refundBankAccount:roles fill:#99bcdb,stroke:white
role:refundBankAccount:owner[[refundBankAccount:owner]]
role:refundBankAccount:admin[[refundBankAccount:admin]]
role:refundBankAccount:referrer[[refundBankAccount:referrer]]
end
end
%% granting roles to roles
role:global:admin -.-> role:debitorRel.anchorPerson:owner
role:debitorRel.anchorPerson:owner -.-> role:debitorRel.anchorPerson:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel.anchorPerson:referrer
role:global:admin -.-> role:debitorRel.holderPerson:owner
role:debitorRel.holderPerson:owner -.-> role:debitorRel.holderPerson:admin
role:debitorRel.holderPerson:admin -.-> role:debitorRel.holderPerson:referrer
role:global:admin -.-> role:debitorRel.contact:owner
role:debitorRel.contact:owner -.-> role:debitorRel.contact:admin
role:debitorRel.contact:admin -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:debitorRel:owner
role:debitorRel:owner -.-> role:debitorRel:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel:admin
role:debitorRel:admin -.-> role:debitorRel:agent
role:debitorRel.holderPerson:admin -.-> role:debitorRel:agent
role:debitorRel:agent -.-> role:debitorRel:tenant
role:debitorRel.holderPerson:admin -.-> role:debitorRel:tenant
role:debitorRel.contact:admin -.-> role:debitorRel:tenant
role:debitorRel:tenant -.-> role:debitorRel.anchorPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.holderPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:refundBankAccount:owner
role:refundBankAccount:owner -.-> role:refundBankAccount:admin
role:refundBankAccount:admin -.-> role:refundBankAccount:referrer
role:refundBankAccount:admin ==> role:debitorRel:agent
role:debitorRel:agent ==> role:refundBankAccount:referrer
role:global:admin -.-> role:partnerRel.anchorPerson:owner
role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer
role:global:admin -.-> role:partnerRel.holderPerson:owner
role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin
role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer
role:global:admin -.-> role:partnerRel.contact:owner
role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin
role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer
role:global:admin -.-> role:partnerRel:owner
role:partnerRel:owner -.-> role:partnerRel:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin
role:partnerRel:admin -.-> role:partnerRel:agent
role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent
role:partnerRel:agent -.-> role:partnerRel:tenant
role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant
role:partnerRel.contact:admin -.-> role:partnerRel:tenant
role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.contact:referrer
role:partnerRel:admin ==> role:debitorRel:admin
role:partnerRel:agent ==> role:debitorRel:agent
role:debitorRel:agent ==> role:partnerRel:tenant
%% granting permissions to roles
role:global:admin ==> perm:debitor:INSERT
role:debitorRel:owner ==> perm:debitor:DELETE
role:debitorRel:admin ==> perm:debitor:UPDATE
role:debitorRel:tenant ==> perm:debitor:SELECT
```

View File

@ -1,231 +0,0 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--changeset hs-office-debitor-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_debitor');
--//
-- ============================================================================
--changeset hs-office-debitor-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficeDebitor', 'hs_office_debitor');
--//
-- ============================================================================
--changeset hs-office-debitor-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace procedure buildRbacSystemForHsOfficeDebitor(
NEW hs_office_debitor
)
language plpgsql as $$
declare
newPartnerRel hs_office_relation;
newDebitorRel hs_office_relation;
newRefundBankAccount hs_office_bankaccount;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
SELECT * FROM hs_office_relation WHERE uuid = NEW.debitorRelUuid INTO newDebitorRel;
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid);
SELECT * FROM hs_office_bankaccount WHERE uuid = NEW.refundBankAccountUuid INTO newRefundBankAccount;
call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationAgent(newDebitorRel));
call grantRoleToRole(hsOfficeRelationAdmin(newDebitorRel), hsOfficeRelationAdmin(newPartnerRel));
call grantRoleToRole(hsOfficeRelationAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount));
call grantRoleToRole(hsOfficeRelationAgent(newDebitorRel), hsOfficeRelationAgent(newPartnerRel));
call grantRoleToRole(hsOfficeRelationTenant(newPartnerRel), hsOfficeRelationAgent(newDebitorRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationOwner(newDebitorRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newDebitorRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAdmin(newDebitorRel));
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$;
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_debitor row.
*/
create or replace function insertTriggerForHsOfficeDebitor_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeDebitor(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeDebitor_tg
after insert on hs_office_debitor
for each row
execute procedure insertTriggerForHsOfficeDebitor_tf();
--//
-- ============================================================================
--changeset hs-office-debitor-rbac-update-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
*/
create or replace procedure updateRbacRulesForHsOfficeDebitor(
OLD hs_office_debitor,
NEW hs_office_debitor
)
language plpgsql as $$
begin
if NEW.refundBankAccountUuid is distinct from OLD.refundBankAccountUuid then
delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid;
call buildRbacSystemForHsOfficeDebitor(NEW);
end if;
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_debitor row.
*/
create or replace function updateTriggerForHsOfficeDebitor_tf()
returns trigger
language plpgsql
strict as $$
begin
call updateRbacRulesForHsOfficeDebitor(OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerForHsOfficeDebitor_tg
after update on hs_office_debitor
for each row
execute procedure updateTriggerForHsOfficeDebitor_tf();
--//
-- ============================================================================
--changeset hs-office-debitor-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_debitor permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO hs_office_debitor permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
roleUuid := findRoleId(globalAdmin());
permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_debitor');
call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP;
END;
$$;
/**
Adds hs_office_debitor INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_debitor_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_debitor'),
globalAdmin());
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_office_debitor_global_insert_tg
after insert on global
for each row
execute procedure hs_office_debitor_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_debitor,
where only global-admin has that permission.
*/
create or replace function hs_office_debitor_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_debitor not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_debitor_insert_permission_check_tg
before insert on hs_office_debitor
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_debitor_insert_permission_missing_tf();
--//
-- ============================================================================
--changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromQuery('hs_office_debitor',
$idName$
SELECT debitor.uuid,
'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner
JOIN hs_office_relation partnerRel
ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER'
JOIN hs_office_relation debitorRel
ON debitorRel.anchorUuid = partnerRel.holderUuid AND partnerRel.type = 'DEBITOR'
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00')
from hs_office_debitor as debitor
$idName$);
--//
-- ============================================================================
--changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_debitor',
$orderBy$
SELECT debitor.uuid,
'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner
JOIN hs_office_relation partnerRel
ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER'
JOIN hs_office_relation debitorRel
ON debitorRel.anchorUuid = partnerRel.holderUuid AND partnerRel.type = 'DEBITOR'
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00')
from hs_office_debitor as debitor
$orderBy$,
$updates$
debitorRel = new.debitorRel,
billable = new.billable,
debitorUuid = new.debitorUuid,
refundBankAccountUuid = new.refundBankAccountUuid,
vatId = new.vatId,
vatCountryCode = new.vatCountryCode,
vatBusiness = new.vatBusiness,
vatReverseCharge = new.vatReverseCharge,
defaultPrefix = new.defaultPrefix
$updates$);
--//

View File

@ -1,250 +1,275 @@
### hs_office_debitor RBAC Roles ### rbac debitor
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
subgraph global subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
style global fill:#eee direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:global.admin[global.admin]
end
subgraph office subgraph debitorRel.anchorPerson:roles[ ]
style office fill:#eee style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
subgraph sepa
subgraph bankaccount
style bankaccount fill: #e9f7ef
user:hsOfficeBankAccount.creator([bankaccount.creator])
role:hsOfficeBankAccount.owner[bankaccount.owner]
%% permissions
role:hsOfficeBankAccount.owner --> perm:hsOfficeBankAccount.*{{bankaccount.*}}
%% incoming
role:global.admin --> role:hsOfficeBankAccount.owner
user:hsOfficeBankAccount.creator ---> role:hsOfficeBankAccount.owner
role:hsOfficeBankAccount.admin[bankaccount.admin]
%% permissions
role:hsOfficeBankAccount.admin --> perm:hsOfficeBankAccount.edit{{bankaccount.edit}}
%% incoming
role:hsOfficeBankAccount.owner ---> role:hsOfficeBankAccount.admin
role:hsOfficeBankAccount.tenant[bankaccount.tenant]
%% incoming
role:hsOfficeBankAccount.admin ---> role:hsOfficeBankAccount.tenant
role:hsOfficeBankAccount.guest[bankaccount.guest]
%% permissions
role:hsOfficeBankAccount.guest --> perm:hsOfficeBankAccount.view{{bankaccount.view}}
%% incoming
role:hsOfficeBankAccount.tenant ---> role:hsOfficeBankAccount.guest
end
subgraph hsOfficeSepaMandate
end
end
subgraph contact
style contact fill: #e9f7ef
user:hsOfficeContact.creator([contact.creator])
role:hsOfficeContact.owner[contact.owner]
%% permissions
role:hsOfficeContact.owner --> perm:hsOfficeContact.*{{contact.*}}
%% incoming
role:global.admin --> role:hsOfficeContact.owner
user:hsOfficeContact.creator ---> role:hsOfficeContact.owner
role:hsOfficeContact.admin[contact.admin]
%% permissions
role:hsOfficeContact.admin ---> perm:hsOfficeContact.edit{{contact.edit}}
%% incoming
role:hsOfficeContact.owner ---> role:hsOfficeContact.admin
role:hsOfficeContact.tenant[contact.tenant]
%% incoming
role:hsOfficeContact.admin ----> role:hsOfficeContact.tenant
role:hsOfficeContact.guest[contact.guest]
%% permissions
role:hsOfficeContact.guest --> perm:hsOfficeContact.view{{contact.view}}
%% incoming
role:hsOfficeContact.tenant ---> role:hsOfficeContact.guest
end
subgraph partner-person
subgraph person
style person fill: #e9f7ef
user:hsOfficePerson.creator([personcreator])
role:hsOfficePerson.owner[person.owner]
%% permissions
role:hsOfficePerson.owner --> perm:hsOfficePerson.*{{person.*}}
%% incoming
user:hsOfficePerson.creator ---> role:hsOfficePerson.owner
role:global.admin --> role:hsOfficePerson.owner
role:hsOfficePerson.admin[person.admin]
%% permissions
role:hsOfficePerson.admin --> perm:hsOfficePerson.edit{{person.edit}}
%% incoming
role:hsOfficePerson.owner ---> role:hsOfficePerson.admin
role:hsOfficePerson.tenant[person.tenant]
%% incoming
role:hsOfficePerson.admin -----> role:hsOfficePerson.tenant
role:hsOfficePerson.guest[person.guest]
%% permissions
role:hsOfficePerson.guest --> perm:hsOfficePerson.edit{{person.view}}
%% incoming
role:hsOfficePerson.tenant ---> role:hsOfficePerson.guest
end
subgraph partner
role:hsOfficePartner.owner[partner.owner]
%% permissions
role:hsOfficePartner.owner --> perm:hsOfficePartner.*{{partner.*}}
%% incoming
role:global.admin ---> role:hsOfficePartner.owner
role:hsOfficePartner.admin[partner.admin]
%% permissions
role:hsOfficePartner.admin --> perm:hsOfficePartner.edit{{partner.edit}}
%% incoming
role:hsOfficePartner.owner ---> role:hsOfficePartner.admin
%% outgoing
role:hsOfficePartner.admin --> role:hsOfficePerson.tenant
role:hsOfficePartner.admin --> role:hsOfficeContact.tenant
role:hsOfficePartner.agent[partner.agent]
%% incoming
role:hsOfficePartner.admin --> role:hsOfficePartner.agent
role:hsOfficePerson.admin --> role:hsOfficePartner.agent
role:hsOfficeContact.admin --> role:hsOfficePartner.agent
role:hsOfficePartner.tenant[partner.tenant]
%% incoming
role:hsOfficePartner.agent ---> role:hsOfficePartner.tenant
%% outgoing
role:hsOfficePartner.tenant --> role:hsOfficePerson.guest
role:hsOfficePartner.tenant --> role:hsOfficeContact.guest
role:hsOfficePartner.guest[partner.guest]
%% permissions
role:hsOfficePartner.guest --> perm:hsOfficePartner.view{{partner.view}}
%% incoming
role:hsOfficePartner.tenant ---> role:hsOfficePartner.guest
end
end
subgraph debitor
style debitor stroke-width:6px
user:hsOfficeDebitor.creator([debitor.creator])
%% created by role
user:hsOfficeDebitor.creator --> role:hsOfficePartner.agent
role:hsOfficeDebitor.owner[debitor.owner]
%% permissions
role:hsOfficeDebitor.owner --> perm:hsOfficeDebitor.*{{debitor.*}}
%% incoming
user:hsOfficeDebitor.creator --> role:hsOfficeDebitor.owner
role:global.admin --> role:hsOfficeDebitor.owner
role:hsOfficeDebitor.admin[debitor.admin]
%% permissions
role:hsOfficeDebitor.admin --> perm:hsOfficeDebitor.edit{{debitor.edit}}
%% incoming
role:hsOfficeDebitor.owner ---> role:hsOfficeDebitor.admin
role:hsOfficeDebitor.agent[debitor.agent]
%% incoming
role:hsOfficeDebitor.admin ---> role:hsOfficeDebitor.agent
role:hsOfficePartner.admin --> role:hsOfficeDebitor.agent
%% outgoing
role:hsOfficeDebitor.agent --> role:hsOfficeBankAccount.tenant
role:hsOfficeDebitor.tenant[debitor.tenant]
%% incoming
role:hsOfficeDebitor.agent ---> role:hsOfficeDebitor.tenant
role:hsOfficePartner.agent --> role:hsOfficeDebitor.tenant
role:hsOfficeBankAccount.admin --> role:hsOfficeDebitor.tenant
%% outgoing
role:hsOfficeDebitor.tenant --> role:hsOfficePartner.tenant
role:hsOfficeDebitor.tenant --> role:hsOfficeContact.guest
role:hsOfficeDebitor.guest[debitor.guest]
%% permissions
role:hsOfficeDebitor.guest --> perm:hsOfficeDebitor.view{{debitor.view}}
%% incoming
role:hsOfficeDebitor.tenant --> role:hsOfficeDebitor.guest
end
end
subgraph hsOfficeSepaMandate role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:hsOfficeSepaMandate.owner[sepaMandate.owner] role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
%% permissions
role:hsOfficeSepaMandate.owner --> perm:hsOfficeSepaMandate.*{{sepaMandate.*}}
%% incoming
role:global.admin ---> role:hsOfficeSepaMandate.owner
role:hsOfficeSepaMandate.admin[sepaMandate.admin]
%% permissions
role:hsOfficeSepaMandate.admin --> perm:hsOfficeSepaMandate.edit{{sepaMandate.edit}}
%% incoming
role:hsOfficeSepaMandate.owner ---> role:hsOfficeSepaMandate.admin
role:hsOfficeSepaMandate.agent[sepaMandate.agent]
%% incoming
role:hsOfficeSepaMandate.admin ---> role:hsOfficeSepaMandate.agent
role:hsOfficeDebitor.admin --> role:hsOfficeSepaMandate.agent
role:hsOfficeBankAccount.admin --> role:hsOfficeSepaMandate.agent
%% outgoing
role:hsOfficeSepaMandate.agent --> role:hsOfficeDebitor.tenant
role:hsOfficeSepaMandate.admin --> role:hsOfficeBankAccount.tenant
role:hsOfficeSepaMandate.tenant[sepaMandate.tenant]
%% incoming
role:hsOfficeSepaMandate.agent --> role:hsOfficeSepaMandate.tenant
%% outgoing
role:hsOfficeSepaMandate.tenant --> role:hsOfficeDebitor.guest
role:hsOfficeSepaMandate.tenant --> role:hsOfficeBankAccount.guest
role:hsOfficeSepaMandate.guest[sepaMandate.guest]
%% permissions
role:hsOfficeSepaMandate.guest --> perm:hsOfficeSepaMandate.view{{sepaMandate.view}}
%% incoming
role:hsOfficeSepaMandate.tenant --> role:hsOfficeSepaMandate.guest
end
subgraph hosting
style hosting fill:#eee
subgraph package
style package fill: #e9f7ef
role:package.owner[package.owner]
--> role:package.admin[package.admin]
--> role:package.tenant[package.tenant]
role:hsOfficeDebitor.agent --> role:package.owner
role:package.admin --> role:hsOfficeDebitor.tenant
role:hsOfficePartner.tenant --> role:hsOfficeDebitor.guest
end end
end end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph debitor["`**debitor**`"]
direction TB
style debitor fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph debitor:permissions[ ]
style debitor:permissions fill:#dd4901,stroke:white
perm:debitor:INSERT{{debitor:INSERT}}
perm:debitor:DELETE{{debitor:DELETE}}
perm:debitor:UPDATE{{debitor:UPDATE}}
perm:debitor:SELECT{{debitor:SELECT}}
end
subgraph debitorRel["`**debitorRel**`"]
direction TB
style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.anchorPerson:roles[ ]
style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]]
role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]]
role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]]
end
end
subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"]
direction TB
style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.holderPerson:roles[ ]
style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white
role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]]
role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]]
role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]]
end
end
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact:roles[ ]
style debitorRel.contact:roles fill:#99bcdb,stroke:white
role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end
subgraph debitorRel:roles[ ]
style debitorRel:roles fill:#99bcdb,stroke:white
role:debitorRel:owner[[debitorRel:owner]]
role:debitorRel:admin[[debitorRel:admin]]
role:debitorRel:agent[[debitorRel:agent]]
role:debitorRel:tenant[[debitorRel:tenant]]
end
end
end
subgraph partnerRel["`**partnerRel**`"]
direction TB
style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel:roles[ ]
style partnerRel:roles fill:#99bcdb,stroke:white
role:partnerRel:owner[[partnerRel:owner]]
role:partnerRel:admin[[partnerRel:admin]]
role:partnerRel:agent[[partnerRel:agent]]
role:partnerRel:tenant[[partnerRel:tenant]]
end
end
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph debitorRel.contact:roles[ ]
style debitorRel.contact:roles fill:#99bcdb,stroke:white
role:debitorRel.contact:owner[[debitorRel.contact:owner]]
role:debitorRel.contact:admin[[debitorRel.contact:admin]]
role:debitorRel.contact:referrer[[debitorRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph refundBankAccount["`**refundBankAccount**`"]
direction TB
style refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph refundBankAccount:roles[ ]
style refundBankAccount:roles fill:#99bcdb,stroke:white
role:refundBankAccount:owner[[refundBankAccount:owner]]
role:refundBankAccount:admin[[refundBankAccount:admin]]
role:refundBankAccount:referrer[[refundBankAccount:referrer]]
end
end
%% granting roles to roles
role:global:admin -.-> role:debitorRel.anchorPerson:owner
role:debitorRel.anchorPerson:owner -.-> role:debitorRel.anchorPerson:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel.anchorPerson:referrer
role:global:admin -.-> role:debitorRel.holderPerson:owner
role:debitorRel.holderPerson:owner -.-> role:debitorRel.holderPerson:admin
role:debitorRel.holderPerson:admin -.-> role:debitorRel.holderPerson:referrer
role:global:admin -.-> role:debitorRel.contact:owner
role:debitorRel.contact:owner -.-> role:debitorRel.contact:admin
role:debitorRel.contact:admin -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:debitorRel:owner
role:debitorRel:owner -.-> role:debitorRel:admin
role:debitorRel.anchorPerson:admin -.-> role:debitorRel:admin
role:debitorRel:admin -.-> role:debitorRel:agent
role:debitorRel.holderPerson:admin -.-> role:debitorRel:agent
role:debitorRel:agent -.-> role:debitorRel:tenant
role:debitorRel.holderPerson:admin -.-> role:debitorRel:tenant
role:debitorRel.contact:admin -.-> role:debitorRel:tenant
role:debitorRel:tenant -.-> role:debitorRel.anchorPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.holderPerson:referrer
role:debitorRel:tenant -.-> role:debitorRel.contact:referrer
role:global:admin -.-> role:refundBankAccount:owner
role:refundBankAccount:owner -.-> role:refundBankAccount:admin
role:refundBankAccount:admin -.-> role:refundBankAccount:referrer
role:refundBankAccount:admin ==> role:debitorRel:agent
role:debitorRel:agent ==> role:refundBankAccount:referrer
role:global:admin -.-> role:partnerRel.anchorPerson:owner
role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer
role:global:admin -.-> role:partnerRel.holderPerson:owner
role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin
role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer
role:global:admin -.-> role:partnerRel.contact:owner
role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin
role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer
role:global:admin -.-> role:partnerRel:owner
role:partnerRel:owner -.-> role:partnerRel:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin
role:partnerRel:admin -.-> role:partnerRel:agent
role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent
role:partnerRel:agent -.-> role:partnerRel:tenant
role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant
role:partnerRel.contact:admin -.-> role:partnerRel:tenant
role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.contact:referrer
role:partnerRel:admin ==> role:debitorRel:admin
role:partnerRel:agent ==> role:debitorRel:agent
role:debitorRel:agent ==> role:partnerRel:tenant
%% granting permissions to roles
role:global:admin ==> perm:debitor:INSERT
role:debitorRel:owner ==> perm:debitor:DELETE
role:debitorRel:admin ==> perm:debitor:UPDATE
role:debitorRel:tenant ==> perm:debitor:SELECT
``` ```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-debitor-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-debitor-rbac-OBJECT:1 endDelimiter:--//
@ -15,233 +17,211 @@ call generateRbacRoleDescriptors('hsOfficeDebitor', 'hs_office_debitor');
-- ============================================================================ -- ============================================================================
--changeset hs-office-debitor-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-debitor-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates and updates the roles and their assignments for debitor entities. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function hsOfficeDebitorRbacRolesTrigger() create or replace procedure buildRbacSystemForHsOfficeDebitor(
returns trigger NEW hs_office_debitor
language plpgsql )
strict as $$ language plpgsql as $$
declare declare
hsOfficeDebitorTenant RbacRoleDescriptor; newPartnerRel hs_office_relation;
oldPartner hs_office_partner; newDebitorRel hs_office_relation;
newPartner hs_office_partner; newRefundBankAccount hs_office_bankaccount;
newPerson hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
newBankAccount hs_office_bankaccount;
oldBankAccount hs_office_bankaccount;
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
hsOfficeDebitorTenant := hsOfficeDebitorTenant(NEW); SELECT partnerRel.*
FROM hs_office_relation AS partnerRel
JOIN hs_office_relation AS debitorRel
ON debitorRel.type = 'DEBITOR' AND debitorRel.anchorUuid = partnerRel.holderUuid
WHERE partnerRel.type = 'PARTNER'
AND NEW.debitorRelUuid = debitorRel.uuid
INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid);
select * from hs_office_partner as p where p.uuid = NEW.partnerUuid into newPartner; SELECT * FROM hs_office_relation WHERE uuid = NEW.debitorRelUuid INTO newDebitorRel;
select * from hs_office_person as p where p.uuid = newPartner.personUuid into newPerson; assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid);
select * from hs_office_contact as c where c.uuid = NEW.billingContactUuid into newContact;
select * from hs_office_bankaccount as b where b.uuid = NEW.refundBankAccountUuid into newBankAccount;
if TG_OP = 'INSERT' then
SELECT * FROM hs_office_bankaccount WHERE uuid = NEW.refundBankAccountUuid INTO newRefundBankAccount;
perform createRoleWithGrants( call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationAgent(newDebitorRel));
hsOfficeDebitorOwner(NEW), call grantRoleToRole(hsOfficeRelationAdmin(newDebitorRel), hsOfficeRelationAdmin(newPartnerRel));
permissions => array['DELETE'], call grantRoleToRole(hsOfficeRelationAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount));
incomingSuperRoles => array[globalAdmin()], call grantRoleToRole(hsOfficeRelationAgent(newDebitorRel), hsOfficeRelationAgent(newPartnerRel));
userUuids => array[currentUserUuid()], call grantRoleToRole(hsOfficeRelationTenant(newPartnerRel), hsOfficeRelationAgent(newDebitorRel));
grantedByRole => globalAdmin()
);
perform createRoleWithGrants( call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationOwner(newDebitorRel));
hsOfficeDebitorAdmin(NEW), call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newDebitorRel));
permissions => array['UPDATE'], call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAdmin(newDebitorRel));
incomingSuperRoles => array[hsOfficeDebitorOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeDebitorAgent(NEW),
incomingSuperRoles => array[
hsOfficeDebitorAdmin(NEW),
hsOfficePartnerAdmin(newPartner),
hsOfficeContactAdmin(newContact)],
outgoingSubRoles => array[
hsOfficeBankAccountTenant(newBankaccount)]
);
perform createRoleWithGrants(
hsOfficeDebitorTenant(NEW),
incomingSuperRoles => array[
hsOfficeDebitorAgent(NEW),
hsOfficePartnerAgent(newPartner),
hsOfficeBankAccountAdmin(newBankaccount)],
outgoingSubRoles => array[
hsOfficePartnerTenant(newPartner),
hsOfficeContactGuest(newContact),
hsOfficeBankAccountGuest(newBankaccount)]
);
perform createRoleWithGrants(
hsOfficeDebitorGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeDebitorTenant(NEW)]
);
elsif TG_OP = 'UPDATE' then
if OLD.partnerUuid <> NEW.partnerUuid then
select * from hs_office_partner as p where p.uuid = OLD.partnerUuid into oldPartner;
call revokeRoleFromRole(hsOfficeDebitorAgent(OLD), hsOfficePartnerAdmin(oldPartner));
call grantRoleToRole(hsOfficeDebitorAgent(NEW), hsOfficePartnerAdmin(newPartner));
call revokeRoleFromRole(hsOfficeDebitorTenant(OLD), hsOfficePartnerAgent(oldPartner));
call grantRoleToRole(hsOfficeDebitorTenant(NEW), hsOfficePartnerAgent(newPartner));
call revokeRoleFromRole(hsOfficePartnerTenant(oldPartner), hsOfficeDebitorTenant(OLD));
call grantRoleToRole(hsOfficePartnerTenant(newPartner), hsOfficeDebitorTenant(NEW));
end if;
if OLD.billingContactUuid <> NEW.billingContactUuid then
select * from hs_office_contact as c where c.uuid = OLD.billingContactUuid into oldContact;
call revokeRoleFromRole(hsOfficeDebitorAgent(OLD), hsOfficeContactAdmin(oldContact));
call grantRoleToRole(hsOfficeDebitorAgent(NEW), hsOfficeContactAdmin(newContact));
call revokeRoleFromRole(hsOfficeContactGuest(oldContact), hsOfficeDebitorTenant(OLD));
call grantRoleToRole(hsOfficeContactGuest(newContact), hsOfficeDebitorTenant(NEW));
end if;
if (OLD.refundBankAccountUuid is not null or NEW.refundBankAccountUuid is not null) and
( OLD.refundBankAccountUuid is null or NEW.refundBankAccountUuid is null or
OLD.refundBankAccountUuid <> NEW.refundBankAccountUuid ) then
select * from hs_office_bankaccount as b where b.uuid = OLD.refundBankAccountUuid into oldBankAccount;
if oldBankAccount is not null then
call revokeRoleFromRole(hsOfficeBankAccountTenant(oldBankaccount), hsOfficeDebitorAgent(OLD));
end if;
if newBankAccount is not null then
call grantRoleToRole(hsOfficeBankAccountTenant(newBankaccount), hsOfficeDebitorAgent(NEW));
end if;
if oldBankAccount is not null then
call revokeRoleFromRole(hsOfficeDebitorTenant(OLD), hsOfficeBankAccountAdmin(oldBankaccount));
end if;
if newBankAccount is not null then
call grantRoleToRole(hsOfficeDebitorTenant(NEW), hsOfficeBankAccountAdmin(newBankaccount));
end if;
if oldBankAccount is not null then
call revokeRoleFromRole(hsOfficeBankAccountGuest(oldBankaccount), hsOfficeDebitorTenant(OLD));
end if;
if newBankAccount is not null then
call grantRoleToRole(hsOfficeBankAccountGuest(newBankaccount), hsOfficeDebitorTenant(NEW));
end if;
end if;
else
raise exception 'invalid usage of TRIGGER';
end if;
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
end; $$; end; $$;
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new debitor. AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_debitor row.
*/ */
create trigger createRbacRolesForHsOfficeDebitor_Trigger
after insert
on hs_office_debitor
for each row
execute procedure hsOfficeDebitorRbacRolesTrigger();
/* create or replace function insertTriggerForHsOfficeDebitor_tf()
An AFTER UPDATE TRIGGER which updates the role structure of a debitor. returns trigger
*/ language plpgsql
create trigger updateRbacRolesForHsOfficeDebitor_Trigger strict as $$
after update begin
on hs_office_debitor call buildRbacSystemForHsOfficeDebitor(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeDebitor_tg
after insert on hs_office_debitor
for each row for each row
execute procedure hsOfficeDebitorRbacRolesTrigger(); execute procedure insertTriggerForHsOfficeDebitor_tf();
--// --//
-- ============================================================================
--changeset hs-office-debitor-rbac-update-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
*/
create or replace procedure updateRbacRulesForHsOfficeDebitor(
OLD hs_office_debitor,
NEW hs_office_debitor
)
language plpgsql as $$
begin
if NEW.debitorRelUuid is distinct from OLD.debitorRelUuid
or NEW.refundBankAccountUuid is distinct from OLD.refundBankAccountUuid then
delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid;
call buildRbacSystemForHsOfficeDebitor(NEW);
end if;
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_debitor row.
*/
create or replace function updateTriggerForHsOfficeDebitor_tf()
returns trigger
language plpgsql
strict as $$
begin
call updateRbacRulesForHsOfficeDebitor(OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerForHsOfficeDebitor_tg
after update on hs_office_debitor
for each row
execute procedure updateTriggerForHsOfficeDebitor_tf();
--//
-- ============================================================================
--changeset hs-office-debitor-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_debitor permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
begin
call defineContext('create INSERT INTO hs_office_debitor permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_debitor'),
globalAdmin());
END LOOP;
END;
$$;
/**
Adds hs_office_debitor INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_debitor_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_debitor'),
globalAdmin());
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_office_debitor_global_insert_tg
after insert on global
for each row
execute procedure hs_office_debitor_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_debitor,
where only global-admin has that permission.
*/
create or replace function hs_office_debitor_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_debitor not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_debitor_insert_permission_check_tg
before insert on hs_office_debitor
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_debitor_insert_permission_missing_tf();
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_debitor', $idName$
'#' ||
(select partnerNumber from hs_office_partner p where p.uuid = target.partnerUuid) ||
to_char(debitorNumberSuffix, 'fm00') ||
':' || (select split_part(idName, ':', 2) from hs_office_partner_iv pi where pi.uuid = target.partnerUuid)
$idName$);
--//
call generateRbacIdentityViewFromQuery('hs_office_debitor',
$idName$
SELECT debitor.uuid AS uuid,
'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner
JOIN hs_office_relation partnerRel
ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER'
JOIN hs_office_relation debitorRel
ON debitorRel.anchorUuid = partnerRel.holderUuid AND debitorRel.type = 'DEBITOR'
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00') as idName
FROM hs_office_debitor AS debitor
$idName$);
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_debitor', 'target.debitorNumberSuffix', call generateRbacRestrictedView('hs_office_debitor',
$orderBy$
defaultPrefix
$orderBy$,
$updates$ $updates$
partnerUuid = new.partnerUuid, -- TODO: remove? should never do anything debitorRelUuid = new.debitorRelUuid,
billable = new.billable, billable = new.billable,
billingContactUuid = new.billingContactUuid,
debitorNumberSuffix = new.debitorNumberSuffix, -- TODO: Should it be allowed to updated this value?
refundBankAccountUuid = new.refundBankAccountUuid, refundBankAccountUuid = new.refundBankAccountUuid,
vatId = new.vatId, vatId = new.vatId,
vatCountryCode = new.vatCountryCode, vatCountryCode = new.vatCountryCode,
vatBusiness = new.vatBusiness, vatBusiness = new.vatBusiness,
vatreversecharge = new.vatreversecharge, vatReverseCharge = new.vatReverseCharge,
defaultPrefix = new.defaultPrefix -- TODO: Should it be allowed to updated this value? defaultPrefix = new.defaultPrefix
$updates$); $updates$);
--// --//
-- ============================================================================
--changeset hs-office-debitor-rbac-NEW-DEBITOR:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-debitor and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addDebitorPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-debitor permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addDebitorPermissions := createPermissions(globalObjectUuid, array ['new-debitor']);
call grantPermissionsToRole(globalAdminRoleUuid, addDebitorPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-debitor to current user respectively assumed roles.
*/
create or replace function addHsOfficeDebitorNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-debitor not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new debitor.
*/
create trigger hs_office_debitor_insert_trigger
before insert
on hs_office_debitor
for each row
-- TODO.spec: who is allowed to create new debitors
when ( not hasAssumedRole() )
execute procedure addHsOfficeDebitorNotAllowedForCurrentSubjects();
--//

View File

@ -9,36 +9,41 @@
Creates a single debitor test record. Creates a single debitor test record.
*/ */
create or replace procedure createHsOfficeDebitorTestData( create or replace procedure createHsOfficeDebitorTestData(
debitorNumberSuffix numeric(5), withDebitorNumberSuffix numeric(5),
partnerTradeName varchar, forPartnerPersonName varchar,
billingContactLabel varchar, forBillingContactLabel varchar,
defaultPrefix varchar withDefaultPrefix varchar
) )
language plpgsql as $$ language plpgsql as $$
declare declare
currentTask varchar; currentTask varchar;
idName varchar; idName varchar;
relatedPartner hs_office_partner; relatedDebitorRelUuid uuid;
relatedContact hs_office_contact;
relatedBankAccountUuid uuid; relatedBankAccountUuid uuid;
begin begin
idName := cleanIdentifier( partnerTradeName|| '-' || billingContactLabel); idName := cleanIdentifier( forPartnerPersonName|| '-' || forBillingContactLabel);
currentTask := 'creating debitor test-data ' || idName; currentTask := 'creating debitor test-data ' || idName;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin'); call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask); execute format('set local hsadminng.currentTask to %L', currentTask);
select partner.* from hs_office_partner partner select debitorRel.uuid
join hs_office_person person on person.uuid = partner.personUuid into relatedDebitorRelUuid
where person.tradeName = partnerTradeName into relatedPartner; from hs_office_relation debitorRel
select c.* from hs_office_contact c where c.label = billingContactLabel into relatedContact; join hs_office_person person on person.uuid = debitorRel.holderUuid
select b.uuid from hs_office_bankaccount b where b.holder = partnerTradeName into relatedBankAccountUuid; and (person.tradeName = forPartnerPersonName or person.familyName = forPartnerPersonName)
where debitorRel.type = 'DEBITOR';
raise notice 'creating test debitor: % (#%)', idName, debitorNumberSuffix; select b.uuid
raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner; into relatedBankAccountUuid
raise notice '- using billingContact (%): %', relatedContact.uuid, relatedContact; from hs_office_bankaccount b
where b.holder = forPartnerPersonName;
raise notice 'creating test debitor: % (#%)', idName, withDebitorNumberSuffix;
-- raise exception 'creating test debitor: (uuid=%, debitorRelUuid=%, debitornumbersuffix=%, billable=%, vatbusiness=%, vatreversecharge=%, refundbankaccountuuid=%, defaultprefix=%)',
-- uuid_generate_v4(), relatedDebitorRelUuid, withDebitorNumberSuffix, true, true, false, relatedBankAccountUuid, withDefaultPrefix;
insert insert
into hs_office_debitor (uuid, partneruuid, debitornumbersuffix, billable, billingcontactuuid, vatbusiness, vatreversecharge, refundbankaccountuuid, defaultprefix) into hs_office_debitor (uuid, debitorRelUuid, debitornumbersuffix, billable, vatbusiness, vatreversecharge, refundbankaccountuuid, defaultprefix)
values (uuid_generate_v4(), relatedPartner.uuid, debitorNumberSuffix, true, relatedContact.uuid, true, false, relatedBankAccountUuid, defaultPrefix); values (uuid_generate_v4(), relatedDebitorRelUuid, withDebitorNumberSuffix, true, true, false, relatedBankAccountUuid, withDefaultPrefix);
end; $$; end; $$;
--// --//

View File

@ -12,7 +12,6 @@ create table if not exists hs_office_membership
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
partnerUuid uuid not null references hs_office_partner(uuid), partnerUuid uuid not null references hs_office_partner(uuid),
mainDebitorUuid uuid not null references hs_office_debitor(uuid),
memberNumberSuffix char(2) not null check ( memberNumberSuffix char(2) not null check (
memberNumberSuffix::text ~ '^[0-9][0-9]$'), memberNumberSuffix::text ~ '^[0-9][0-9]$'),
validity daterange not null, validity daterange not null,

View File

@ -1,75 +1,159 @@
### hs_office_membership RBAC ### rbac membership
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
subgraph global subgraph partnerRel["`**partnerRel**`"]
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeDebitor
direction TB direction TB
style hsOfficeDebitor fill:#eee style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeDebitor.owner[debitor.owner] subgraph partnerRel.contact["`**partnerRel.contact**`"]
--> role:hsOfficeDebitor.admin[debitor.admin] direction TB
--> role:hsOfficeDebitor.tenant[debitor.tenant] style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
--> role:hsOfficeDebitor.guest[debitor.guest]
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph partnerRel:roles[ ]
style partnerRel:roles fill:#99bcdb,stroke:white
role:partnerRel:owner[[partnerRel:owner]]
role:partnerRel:admin[[partnerRel:admin]]
role:partnerRel:agent[[partnerRel:agent]]
role:partnerRel:tenant[[partnerRel:tenant]]
end
end end
subgraph hsOfficePartner subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB direction TB
style hsOfficePartner fill:#eee style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficePartner.owner[partner.admin] subgraph partnerRel.contact:roles[ ]
--> role:hsOfficePartner.admin[partner.admin] style partnerRel.contact:roles fill:#99bcdb,stroke:white
--> role:hsOfficePartner.agent[partner.agent]
--> role:hsOfficePartner.tenant[partner.tenant] role:partnerRel.contact:owner[[partnerRel.contact:owner]]
--> role:hsOfficePartner.guest[partner.guest] role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end end
subgraph hsOfficeMembership subgraph membership["`**membership**`"]
direction TB
role:hsOfficeMembership.owner[membership.owner] style membership fill:#dd4901,stroke:#274d6e,stroke-width:8px
%% permissions
role:hsOfficeMembership.owner --> perm:hsOfficeMembership.*{{membership.*}}
%% incoming
role:global.admin ---> role:hsOfficeMembership.owner
role:hsOfficeMembership.admin[membership.admin]
%% permissions
role:hsOfficeMembership.admin --> perm:hsOfficeMembership.edit{{membership.edit}}
%% incoming
role:hsOfficeMembership.owner ---> role:hsOfficeMembership.admin
role:hsOfficeMembership.agent[membership.agent]
%% incoming
role:hsOfficeMembership.admin ---> role:hsOfficeMembership.agent
role:hsOfficePartner.admin --> role:hsOfficeMembership.agent
role:hsOfficeDebitor.admin --> role:hsOfficeMembership.agent
%% outgoing
role:hsOfficeMembership.agent --> role:hsOfficePartner.tenant
role:hsOfficeMembership.agent --> role:hsOfficeDebitor.tenant
role:hsOfficeMembership.tenant[membership.tenant]
%% incoming
role:hsOfficeMembership.agent --> role:hsOfficeMembership.tenant
role:hsOfficePartner.agent --> role:hsOfficeMembership.tenant
role:hsOfficeDebitor.agent --> role:hsOfficeMembership.tenant
%% outgoing
role:hsOfficeMembership.tenant --> role:hsOfficePartner.guest
role:hsOfficeMembership.tenant --> role:hsOfficeDebitor.guest
role:hsOfficeMembership.guest[membership.guest] subgraph membership:roles[ ]
%% permissions style membership:roles fill:#dd4901,stroke:white
role:hsOfficeMembership.guest --> perm:hsOfficeMembership.view{{membership.view}}
%% incoming role:membership:owner[[membership:owner]]
role:hsOfficeMembership.tenant --> role:hsOfficeMembership.guest role:membership:admin[[membership:admin]]
role:hsOfficePartner.tenant --> role:hsOfficeMembership.guest role:membership:referrer[[membership:referrer]]
role:hsOfficeDebitor.tenant --> role:hsOfficeMembership.guest end
subgraph membership:permissions[ ]
style membership:permissions fill:#dd4901,stroke:white
perm:membership:INSERT{{membership:INSERT}}
perm:membership:DELETE{{membership:DELETE}}
perm:membership:UPDATE{{membership:UPDATE}}
perm:membership:SELECT{{membership:SELECT}}
end
end end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
%% granting roles to users
user:creator ==> role:membership:owner
%% granting roles to roles
role:global:admin -.-> role:partnerRel.anchorPerson:owner
role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer
role:global:admin -.-> role:partnerRel.holderPerson:owner
role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin
role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer
role:global:admin -.-> role:partnerRel.contact:owner
role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin
role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer
role:global:admin -.-> role:partnerRel:owner
role:partnerRel:owner -.-> role:partnerRel:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin
role:partnerRel:admin -.-> role:partnerRel:agent
role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent
role:partnerRel:agent -.-> role:partnerRel:tenant
role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant
role:partnerRel.contact:admin -.-> role:partnerRel:tenant
role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.contact:referrer
role:partnerRel:admin ==> role:membership:owner
role:membership:owner ==> role:membership:admin
role:partnerRel:agent ==> role:membership:admin
role:membership:admin ==> role:membership:referrer
role:membership:referrer ==> role:partnerRel:tenant
%% granting permissions to roles
role:global:admin ==> perm:membership:INSERT
role:membership:owner ==> perm:membership:DELETE
role:membership:admin ==> perm:membership:UPDATE
role:membership:referrer ==> perm:membership:SELECT
``` ```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================ -- ============================================================================
--changeset hs-office-membership-rbac-OBJECT:1 endDelimiter:--// --changeset hs-office-membership-rbac-OBJECT:1 endDelimiter:--//
@ -15,148 +17,162 @@ call generateRbacRoleDescriptors('hsOfficeMembership', 'hs_office_membership');
-- ============================================================================ -- ============================================================================
--changeset hs-office-membership-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-membership-rbac-insert-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates and updates the roles and their assignments for membership entities. Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace function hsOfficeMembershipRbacRolesTrigger() create or replace procedure buildRbacSystemForHsOfficeMembership(
returns trigger NEW hs_office_membership
language plpgsql )
strict as $$ language plpgsql as $$
declare declare
newHsOfficePartner hs_office_partner; newPartnerRel hs_office_relation;
newHsOfficeDebitor hs_office_debitor;
begin begin
call enterTriggerForObjectUuid(NEW.uuid); call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_partner as p where p.uuid = NEW.partnerUuid into newHsOfficePartner; SELECT partnerRel.*
select * from hs_office_debitor as c where c.uuid = NEW.mainDebitorUuid into newHsOfficeDebitor; FROM hs_office_partner AS partner
JOIN hs_office_relation AS partnerRel ON partnerRel.uuid = partner.partnerRelUuid
WHERE partner.uuid = NEW.partnerUuid
INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerUuid = %s', NEW.partnerUuid);
if TG_OP = 'INSERT' then
-- === ATTENTION: code generated from related Mermaid flowchart: === perform createRoleWithGrants(
hsOfficeMembershipOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[hsOfficeRelationAdmin(newPartnerRel)],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeMembershipOwner(NEW), hsOfficeMembershipAdmin(NEW),
permissions => array['DELETE'], permissions => array['UPDATE'],
incomingSuperRoles => array[globalAdmin()] incomingSuperRoles => array[
); hsOfficeMembershipOwner(NEW),
hsOfficeRelationAgent(newPartnerRel)]
);
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeMembershipAdmin(NEW), hsOfficeMembershipReferrer(NEW),
permissions => array['UPDATE'], permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)] incomingSuperRoles => array[hsOfficeMembershipAdmin(NEW)],
); outgoingSubRoles => array[hsOfficeRelationTenant(newPartnerRel)]
);
perform createRoleWithGrants(
hsOfficeMembershipAgent(NEW),
incomingSuperRoles => array[hsOfficeMembershipAdmin(NEW), hsOfficePartnerAdmin(newHsOfficePartner), hsOfficeDebitorAdmin(newHsOfficeDebitor)],
outgoingSubRoles => array[hsOfficePartnerTenant(newHsOfficePartner), hsOfficeDebitorTenant(newHsOfficeDebitor)]
);
perform createRoleWithGrants(
hsOfficeMembershipTenant(NEW),
incomingSuperRoles => array[hsOfficeMembershipAgent(NEW), hsOfficePartnerAgent(newHsOfficePartner), hsOfficeDebitorAgent(newHsOfficeDebitor)],
outgoingSubRoles => array[hsOfficePartnerGuest(newHsOfficePartner), hsOfficeDebitorGuest(newHsOfficeDebitor)]
);
perform createRoleWithGrants(
hsOfficeMembershipGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeMembershipTenant(NEW), hsOfficePartnerTenant(newHsOfficePartner), hsOfficeDebitorTenant(newHsOfficeDebitor)]
);
-- === END of code generated from Mermaid flowchart. ===
else
raise exception 'invalid usage of TRIGGER';
end if;
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
end; $$; end; $$;
/* /*
An AFTER INSERT TRIGGER which creates the role structure for a new customer. AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_membership row.
*/ */
create trigger createRbacRolesForHsOfficeMembership_Trigger
after insert create or replace function insertTriggerForHsOfficeMembership_tf()
on hs_office_membership returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeMembership(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeMembership_tg
after insert on hs_office_membership
for each row for each row
execute procedure hsOfficeMembershipRbacRolesTrigger(); execute procedure insertTriggerForHsOfficeMembership_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset hs-office-membership-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-membership-rbac-INSERT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_membership', $idName$
'#' || /*
(select partnerNumber from hs_office_partner p where p.uuid = target.partnerUuid) || Creates INSERT INTO hs_office_membership permissions for the related global rows.
memberNumberSuffix || */
':' || (select split_part(idName, ':', 2) from hs_office_partner_iv p where p.uuid = target.partnerUuid) do language plpgsql $$
$idName$); declare
row global;
begin
call defineContext('create INSERT INTO hs_office_membership permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_membership'),
globalAdmin());
END LOOP;
END;
$$;
/**
Adds hs_office_membership INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_membership_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_membership'),
globalAdmin());
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_office_membership_global_insert_tg
after insert on global
for each row
execute procedure hs_office_membership_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_membership,
where only global-admin has that permission.
*/
create or replace function hs_office_membership_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_membership not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger hs_office_membership_insert_permission_check_tg
before insert on hs_office_membership
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_membership_insert_permission_missing_tf();
--// --//
-- ============================================================================
--changeset hs-office-membership-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromQuery('hs_office_membership',
$idName$
SELECT m.uuid AS uuid,
'M-' || p.partnerNumber || m.memberNumberSuffix as idName
FROM hs_office_membership AS m
JOIN hs_office_partner AS p ON p.uuid = m.partnerUuid
$idName$);
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-membership-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-membership-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_membership', call generateRbacRestrictedView('hs_office_membership',
orderby => 'target.memberNumberSuffix', $orderBy$
columnUpdates => $updates$ validity
$orderBy$,
$updates$
validity = new.validity, validity = new.validity,
reasonForTermination = new.reasonForTermination, membershipFeeBillable = new.membershipFeeBillable,
membershipFeeBillable = new.membershipFeeBillable reasonForTermination = new.reasonForTermination
$updates$); $updates$);
--// --//
-- ============================================================================
--changeset hs-office-membership-rbac-NEW-Membership:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-membership and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-membership permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-membership']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeMembershipNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-membership not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_membership_insert_trigger
before insert
on hs_office_membership
for each row
-- TODO.spec: who is allowed to create new memberships
when ( not hasAssumedRole() )
execute procedure addHsOfficeMembershipNotAllowedForCurrentSubjects();
--//

View File

@ -9,35 +9,27 @@
Creates a single membership test record. Creates a single membership test record.
*/ */
create or replace procedure createHsOfficeMembershipTestData( create or replace procedure createHsOfficeMembershipTestData(
forPartnerTradeName varchar, forPartnerNumber numeric(5),
forMainDebitorNumberSuffix numeric,
newMemberNumberSuffix char(2) ) newMemberNumberSuffix char(2) )
language plpgsql as $$ language plpgsql as $$
declare declare
currentTask varchar; currentTask varchar;
idName varchar;
relatedPartner hs_office_partner; relatedPartner hs_office_partner;
relatedDebitor hs_office_debitor;
begin begin
idName := cleanIdentifier( forPartnerTradeName || '#' || forMainDebitorNumberSuffix); currentTask := 'creating Membership test-data ' ||
currentTask := 'creating Membership test-data ' || idName; 'P-' || forPartnerNumber::text ||
'M-...' || newMemberNumberSuffix;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin'); call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask); execute format('set local hsadminng.currentTask to %L', currentTask);
select partner.* from hs_office_partner partner select partner.* from hs_office_partner partner
join hs_office_person person on person.uuid = partner.personUuid where partner.partnerNumber = forPartnerNumber into relatedPartner;
where person.tradeName = forPartnerTradeName into relatedPartner;
select d.* from hs_office_debitor d
where d.partneruuid = relatedPartner.uuid
and d.debitorNumberSuffix = forMainDebitorNumberSuffix
into relatedDebitor;
raise notice 'creating test Membership: %', idName; raise notice 'creating test Membership: M-% %', forPartnerNumber, newMemberNumberSuffix;
raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner; raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner;
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
insert insert
into hs_office_membership (uuid, partneruuid, maindebitoruuid, memberNumberSuffix, validity, reasonfortermination) into hs_office_membership (uuid, partneruuid, memberNumberSuffix, validity, reasonfortermination)
values (uuid_generate_v4(), relatedPartner.uuid, relatedDebitor.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'NONE'); values (uuid_generate_v4(), relatedPartner.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'NONE');
end; $$; end; $$;
--// --//
@ -48,9 +40,9 @@ end; $$;
do language plpgsql $$ do language plpgsql $$
begin begin
call createHsOfficeMembershipTestData('First GmbH', 11, '01'); call createHsOfficeMembershipTestData(10001, '01');
call createHsOfficeMembershipTestData('Second e.K.', 12, '02'); call createHsOfficeMembershipTestData(10002, '02');
call createHsOfficeMembershipTestData('Third OHG', 13, '03'); call createHsOfficeMembershipTestData(10003, '03');
end; end;
$$; $$;
--// --//

View File

@ -42,7 +42,7 @@ begin
-- coopsharestransactions cannot be edited nor deleted, just created+viewed -- coopsharestransactions cannot be edited nor deleted, just created+viewed
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeMembershipTenant(newHsOfficeMembership)), getRoleId(hsOfficeMembershipReferrer(newHsOfficeMembership)),
createPermissions(NEW.uuid, array ['SELECT']) createPermissions(NEW.uuid, array ['SELECT'])
); );

View File

@ -42,7 +42,7 @@ begin
-- coopassetstransactions cannot be edited nor deleted, just created+viewed -- coopassetstransactions cannot be edited nor deleted, just created+viewed
call grantPermissionsToRole( call grantPermissionsToRole(
getRoleId(hsOfficeMembershipTenant(newHsOfficeMembership)), getRoleId(hsOfficeMembershipReferrer(newHsOfficeMembership)),
createPermissions(NEW.uuid, array ['SELECT']) createPermissions(NEW.uuid, array ['SELECT'])
); );

View File

@ -129,7 +129,8 @@ public class ArchitectureTest {
public static final ArchRule hsOfficeBankAccountPackageRule = classes() public static final ArchRule hsOfficeBankAccountPackageRule = classes()
.that().resideInAPackage("..hs.office.bankaccount..") .that().resideInAPackage("..hs.office.bankaccount..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.bankaccount..", .resideInAnyPackage(
"..hs.office.bankaccount..",
"..hs.office.sepamandate..", "..hs.office.sepamandate..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.migration.."); "..hs.office.migration..");
@ -139,7 +140,8 @@ public class ArchitectureTest {
public static final ArchRule hsOfficeSepaMandatePackageRule = classes() public static final ArchRule hsOfficeSepaMandatePackageRule = classes()
.that().resideInAPackage("..hs.office.sepamandate..") .that().resideInAPackage("..hs.office.sepamandate..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.sepamandate..", .resideInAnyPackage(
"..hs.office.sepamandate..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.migration.."); "..hs.office.migration..");
@ -148,7 +150,9 @@ public class ArchitectureTest {
public static final ArchRule hsOfficeContactPackageRule = classes() public static final ArchRule hsOfficeContactPackageRule = classes()
.that().resideInAPackage("..hs.office.contact..") .that().resideInAPackage("..hs.office.contact..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.contact..", "..hs.office.relation..", .resideInAnyPackage(
"..hs.office.contact..",
"..hs.office.relation..",
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
@ -159,37 +163,46 @@ public class ArchitectureTest {
public static final ArchRule hsOfficePersonPackageRule = classes() public static final ArchRule hsOfficePersonPackageRule = classes()
.that().resideInAPackage("..hs.office.person..") .that().resideInAPackage("..hs.office.person..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.person..", "..hs.office.relation..", .resideInAnyPackage(
"..hs.office.person..",
"..hs.office.relation..",
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
"..hs.office.migration.."); "..hs.office.migration..")
.orShould().haveNameNotMatching(".*Test$");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeRelationPackageRule = classes() public static final ArchRule hsOfficeRelationPackageRule = classes()
.that().resideInAPackage("..hs.office.relation..") .that().resideInAPackage("..hs.office.relation..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.relation..", .resideInAnyPackage(
"..hs.office.relation..",
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.migration.."); "..hs.office.migration..")
.orShould().haveNameNotMatching(".*Test$");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficePartnerPackageRule = classes() public static final ArchRule hsOfficePartnerPackageRule = classes()
.that().resideInAPackage("..hs.office.partner..") .that().resideInAPackage("..hs.office.partner..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.partner..", .resideInAnyPackage(
"..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
"..hs.office.migration.."); "..hs.office.migration..")
.orShould().haveNameNotMatching(".*Test$");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeMembershipPackageRule = classes() public static final ArchRule hsOfficeMembershipPackageRule = classes()
.that().resideInAPackage("..hs.office.membership..") .that().resideInAPackage("..hs.office.membership..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.membership..", .resideInAnyPackage(
"..hs.office.membership..",
"..hs.office.coopassets..", "..hs.office.coopassets..",
"..hs.office.coopshares..", "..hs.office.coopshares..",
"..hs.office.migration.."); "..hs.office.migration..");

View File

@ -19,7 +19,7 @@ class HsOfficeBankAccountEntityUnitTest {
.iban("DE02370502990000684712") .iban("DE02370502990000684712")
.bic("COKSDE33") .bic("COKSDE33")
.build(); .build();
assertThat("" + givenBankAccount).isEqualTo("bankAccount(holder='given holder', iban='DE02370502990000684712', bic='COKSDE33')"); assertThat(givenBankAccount.toString()).isEqualTo("bankAccount(DE02370502990000684712: holder='given holder', bic='COKSDE33')");
} }
@Test @Test

View File

@ -102,23 +102,21 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTestWithC
final var roles = rawRoleRepo.findAll(); final var roles = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(roles)).containsExactlyInAnyOrder(Array.from( assertThat(distinctRoleNamesOf(roles)).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_office_bankaccount#sometempaccC.owner", "hs_office_bankaccount#DE25500105176934832579.owner",
"hs_office_bankaccount#sometempaccC.admin", "hs_office_bankaccount#DE25500105176934832579.admin",
"hs_office_bankaccount#sometempaccC.tenant", "hs_office_bankaccount#DE25500105176934832579.referrer"
"hs_office_bankaccount#sometempaccC.guest"
)); ));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted( assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, initialGrantNames,
"{ grant perm DELETE on hs_office_bankaccount#sometempaccC to role hs_office_bankaccount#sometempaccC.owner by system and assume }", "{ grant perm DELETE on hs_office_bankaccount#DE25500105176934832579 to role hs_office_bankaccount#DE25500105176934832579.owner by system and assume }",
"{ grant role hs_office_bankaccount#sometempaccC.owner to role global#global.admin by system and assume }", "{ grant role hs_office_bankaccount#DE25500105176934832579.owner to role global#global.admin by system and assume }",
"{ grant role hs_office_bankaccount#sometempaccC.owner to user selfregistered-user-drew@hostsharing.org by global#global.admin and assume }", "{ grant role hs_office_bankaccount#DE25500105176934832579.owner to user selfregistered-user-drew@hostsharing.org by hs_office_bankaccount#DE25500105176934832579.owner and assume }",
"{ grant role hs_office_bankaccount#sometempaccC.admin to role hs_office_bankaccount#sometempaccC.owner by system and assume }", "{ grant role hs_office_bankaccount#DE25500105176934832579.admin to role hs_office_bankaccount#DE25500105176934832579.owner by system and assume }",
"{ grant perm UPDATE on hs_office_bankaccount#DE25500105176934832579 to role hs_office_bankaccount#DE25500105176934832579.admin by system and assume }",
"{ grant role hs_office_bankaccount#sometempaccC.tenant to role hs_office_bankaccount#sometempaccC.admin by system and assume }", "{ grant perm SELECT on hs_office_bankaccount#DE25500105176934832579 to role hs_office_bankaccount#DE25500105176934832579.referrer by system and assume }",
"{ grant role hs_office_bankaccount#DE25500105176934832579.referrer to role hs_office_bankaccount#DE25500105176934832579.admin by system and assume }",
"{ grant perm SELECT on hs_office_bankaccount#sometempaccC to role hs_office_bankaccount#sometempaccC.guest by system and assume }",
"{ grant role hs_office_bankaccount#sometempaccC.guest to role hs_office_bankaccount#sometempaccC.tenant by system and assume }",
null null
)); ));
} }
@ -241,10 +239,6 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTestWithC
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll()); final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()); final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll());
final var givenBankAccount = givenSomeTemporaryBankAccount("selfregistered-user-drew@hostsharing.org"); final var givenBankAccount = givenSomeTemporaryBankAccount("selfregistered-user-drew@hostsharing.org");
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll()).size()).as("unexpected number of roles created")
.isEqualTo(initialRoleNames.size() + 4);
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()).size()).as("unexpected number of grants created")
.isEqualTo(initialGrantNames.size() + 7);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {

View File

@ -105,19 +105,18 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTestWithClean
initialRoleNames, initialRoleNames,
"hs_office_contact#anothernewcontact.owner", "hs_office_contact#anothernewcontact.owner",
"hs_office_contact#anothernewcontact.admin", "hs_office_contact#anothernewcontact.admin",
"hs_office_contact#anothernewcontact.tenant", "hs_office_contact#anothernewcontact.referrer"
"hs_office_contact#anothernewcontact.guest"
)); ));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.from( assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, initialGrantNames,
"{ grant role hs_office_contact#anothernewcontact.owner to role global#global.admin by system and assume }", "{ grant role hs_office_contact#anothernewcontact.owner to role global#global.admin by system and assume }",
"{ grant perm UPDATE on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.admin by system and assume }", "{ grant perm UPDATE on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.admin by system and assume }",
"{ grant role hs_office_contact#anothernewcontact.tenant to role hs_office_contact#anothernewcontact.admin by system and assume }", "{ grant role hs_office_contact#anothernewcontact.owner to user selfregistered-user-drew@hostsharing.org by hs_office_contact#anothernewcontact.owner and assume }",
"{ grant perm DELETE on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.owner by system and assume }", "{ grant perm DELETE on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.owner by system and assume }",
"{ grant role hs_office_contact#anothernewcontact.admin to role hs_office_contact#anothernewcontact.owner by system and assume }", "{ grant role hs_office_contact#anothernewcontact.admin to role hs_office_contact#anothernewcontact.owner by system and assume }",
"{ grant perm SELECT on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.guest by system and assume }",
"{ grant role hs_office_contact#anothernewcontact.guest to role hs_office_contact#anothernewcontact.tenant by system and assume }", "{ grant perm SELECT on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.referrer by system and assume }",
"{ grant role hs_office_contact#anothernewcontact.owner to user selfregistered-user-drew@hostsharing.org by global#global.admin and assume }" "{ grant role hs_office_contact#anothernewcontact.referrer to role hs_office_contact#anothernewcontact.admin by system and assume }"
)); ));
} }

View File

@ -276,7 +276,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
@Test @Test
@Accepts({ "CoopAssetTransaction:X(Access Control)" }) @Accepts({ "CoopAssetTransaction:X(Access Control)" })
void contactAdminUser_canGetRelatedCoopAssetTransaction() { void partnerPersonUser_canGetRelatedCoopAssetTransaction() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenCoopAssetTransactionUuid = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( final var givenCoopAssetTransactionUuid = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(
null, null,
@ -285,7 +285,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "contact-admin@firstcontact.example.com") .header("current-user", "person-FirstGmbH@example.com")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/office/coopassetstransactions/" + givenCoopAssetTransactionUuid) .get("http://localhost/api/hs/office/coopassetstransactions/" + givenCoopAssetTransactionUuid)

View File

@ -23,27 +23,27 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
void toStringContainsAlmostAllPropertiesAccount() { void toStringContainsAlmostAllPropertiesAccount() {
final var result = givenCoopAssetTransaction.toString(); final var result = givenCoopAssetTransaction.toString();
assertThat(result).isEqualTo("CoopAssetsTransaction(1000101, 2020-01-01, DEPOSIT, 128.00, some-ref)"); assertThat(result).isEqualTo("CoopAssetsTransaction(M-1000101: 2020-01-01, DEPOSIT, 128.00, some-ref)");
} }
@Test @Test
void toShortStringContainsOnlyMemberNumberSuffixAndSharesCountOnly() { void toShortStringContainsOnlyMemberNumberSuffixAndSharesCountOnly() {
final var result = givenCoopAssetTransaction.toShortString(); final var result = givenCoopAssetTransaction.toShortString();
assertThat(result).isEqualTo("1000101+128.00"); assertThat(result).isEqualTo("M-1000101:+128.00");
} }
@Test @Test
void toStringWithEmptyTransactionDoesNotThrowException() { void toStringWithEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopAssetsTransaction.toString(); final var result = givenEmptyCoopAssetsTransaction.toString();
assertThat(result).isEqualTo("CoopAssetsTransaction()"); assertThat(result).isEqualTo("CoopAssetsTransaction(M-?????: )");
} }
@Test @Test
void toShortStringEmptyTransactionDoesNotThrowException() { void toShortStringEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopAssetsTransaction.toShortString(); final var result = givenEmptyCoopAssetsTransaction.toShortString();
assertThat(result).isEqualTo("nullnu"); assertThat(result).isEqualTo("M-?????:+0.00");
} }
} }

View File

@ -114,7 +114,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(Array.fromFormatted( .containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, initialGrantNames,
"{ grant perm SELECT on coopassetstransaction#temprefB to role membership#1000101:....tenant by system and assume }", "{ grant perm SELECT on coopassetstransaction#temprefB to role membership#M-1000101.referrer by system and assume }",
null)); null));
} }
@ -141,17 +141,17 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopAssetsTransactionsAreReturned( allTheseCoopAssetsTransactionsAreReturned(
result, result,
"CoopAssetsTransaction(1000101, 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)", "CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)",
"CoopAssetsTransaction(1000101, 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)", "CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)",
"CoopAssetsTransaction(1000101, 2022-10-20, ADJUSTMENT, 128.00, ref 1000101-3, some adjustment)", "CoopAssetsTransaction(M-1000101: 2022-10-20, ADJUSTMENT, 128.00, ref 1000101-3, some adjustment)",
"CoopAssetsTransaction(1000202, 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)", "CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)",
"CoopAssetsTransaction(1000202, 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)", "CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)",
"CoopAssetsTransaction(1000202, 2022-10-20, ADJUSTMENT, 128.00, ref 1000202-3, some adjustment)", "CoopAssetsTransaction(M-1000202: 2022-10-20, ADJUSTMENT, 128.00, ref 1000202-3, some adjustment)",
"CoopAssetsTransaction(1000303, 2010-03-15, DEPOSIT, 320.00, ref 1000303-1, initial deposit)", "CoopAssetsTransaction(M-1000303: 2010-03-15, DEPOSIT, 320.00, ref 1000303-1, initial deposit)",
"CoopAssetsTransaction(1000303, 2021-09-01, DISBURSAL, -128.00, ref 1000303-2, partial disbursal)", "CoopAssetsTransaction(M-1000303: 2021-09-01, DISBURSAL, -128.00, ref 1000303-2, partial disbursal)",
"CoopAssetsTransaction(1000303, 2022-10-20, ADJUSTMENT, 128.00, ref 1000303-3, some adjustment)"); "CoopAssetsTransaction(M-1000303: 2022-10-20, ADJUSTMENT, 128.00, ref 1000303-3, some adjustment)");
} }
@Test @Test
@ -169,9 +169,9 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopAssetsTransactionsAreReturned( allTheseCoopAssetsTransactionsAreReturned(
result, result,
"CoopAssetsTransaction(1000202, 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)", "CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)",
"CoopAssetsTransaction(1000202, 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)", "CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)",
"CoopAssetsTransaction(1000202, 2022-10-20, ADJUSTMENT, 128.00, ref 1000202-3, some adjustment)"); "CoopAssetsTransaction(M-1000202: 2022-10-20, ADJUSTMENT, 128.00, ref 1000202-3, some adjustment)");
} }
@Test @Test
@ -189,13 +189,13 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopAssetsTransactionsAreReturned( allTheseCoopAssetsTransactionsAreReturned(
result, result,
"CoopAssetsTransaction(1000202, 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)"); "CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)");
} }
@Test @Test
public void normalUser_canViewOnlyRelatedCoopAssetsTransactions() { public void partnerPersonAdmin_canViewRelatedCoopAssetsTransactions() {
// given: // given:
context("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.admin"); context("superuser-alex@hostsharing.net", "hs_office_person#FirstGmbH.admin");
// when: // when:
final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(
@ -206,9 +206,9 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then: // then:
exactlyTheseCoopAssetsTransactionsAreReturned( exactlyTheseCoopAssetsTransactionsAreReturned(
result, result,
"CoopAssetsTransaction(1000101, 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)", "CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)",
"CoopAssetsTransaction(1000101, 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)", "CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)",
"CoopAssetsTransaction(1000101, 2022-10-20, ADJUSTMENT, 128.00, ref 1000101-3, some adjustment)"); "CoopAssetsTransaction(M-1000101: 2022-10-20, ADJUSTMENT, 128.00, ref 1000101-3, some adjustment)");
} }
} }

View File

@ -218,17 +218,27 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased
@Test @Test
@Accepts({"CoopShareTransaction:X(Access Control)"}) @Accepts({"CoopShareTransaction:X(Access Control)"})
void contactAdminUser_canGetRelatedCoopShareTransaction() { void partnerPersonUser_canGetRelatedCoopShareTransaction() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenCoopShareTransactionUuid = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(null, LocalDate.of(2010, 3, 15), LocalDate.of(2010, 3, 15)).get(0).getUuid(); final var givenCoopShareTransactionUuid = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(null, LocalDate.of(2010, 3, 15), LocalDate.of(2010, 3, 15)).get(0).getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
.given().header("current-user", "contact-admin@firstcontact.example.com").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions/" + givenCoopShareTransactionUuid).then().log().body().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals(""" .given()
{ .header("current-user", "person-FirstGmbH@example.com")
"transactionType": "SUBSCRIPTION", .port(port)
"shareCount": 4 .when()
} .get("http://localhost/api/hs/office/coopsharestransactions/" + givenCoopShareTransactionUuid)
""")); // @formatter:on .then()
.log().body()
.assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
{
"transactionType": "SUBSCRIPTION",
"shareCount": 4
}
""")); // @formatter:on
} }
} }
} }

View File

@ -88,7 +88,6 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
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()).stream()
.map(s -> s.replace("FirstGmbH-firstcontact", "..."))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.toList(); .toList();
@ -109,11 +108,10 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
final var all = rawRoleRepo.findAll(); final var all = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(initialRoleNames)); // no new roles created assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(initialRoleNames)); // no new roles created
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())) assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("FirstGmbH-firstcontact", "..."))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(Array.fromFormatted( .containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, initialGrantNames,
"{ grant perm SELECT on coopsharestransaction#temprefB to role membership#1000101:....tenant by system and assume }", "{ grant perm SELECT on coopsharestransaction#temprefB to role membership#M-1000101.referrer by system and assume }",
null)); null));
} }
@ -194,7 +192,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
@Test @Test
public void normalUser_canViewOnlyRelatedCoopSharesTransactions() { public void normalUser_canViewOnlyRelatedCoopSharesTransactions() {
// given: // given:
context("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.admin"); context("superuser-alex@hostsharing.net", "hs_office_membership#M-1000101.admin");
// when: // when:
final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(

View File

@ -7,6 +7,9 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.test.Accepts; import net.hostsharing.test.Accepts;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
@ -24,6 +27,7 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -57,6 +61,12 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
@Autowired @Autowired
HsOfficeBankAccountRepository bankAccountRepo; HsOfficeBankAccountRepository bankAccountRepo;
@Autowired
HsOfficePersonRepository personRepo;
@Autowired
HsOfficeRelationRepository relRepo;
@Autowired @Autowired
JpaAttempt jpaAttempt; JpaAttempt jpaAttempt;
@ -81,37 +91,135 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"debitorNumber": 1000111, "debitorRel": {
"debitorNumberSuffix": 11, "anchor": {
"partner": { "person": { "personType": "LEGAL_PERSON" } }, "personType": "LEGAL_PERSON",
"billingContact": { "label": "first contact" }, "tradeName": "First GmbH",
"vatId": null, "givenName": null,
"vatCountryCode": null, "familyName": null
"vatBusiness": true, },
"refundBankAccount": { "holder": "First GmbH" } "holder": {
}, "personType": "LEGAL_PERSON",
{ "tradeName": "First GmbH",
"debitorNumber": 1000212, "givenName": null,
"debitorNumberSuffix": 12, "familyName": null
"partner": { "person": { "tradeName": "Second e.K." } }, },
"billingContact": { "label": "second contact" }, "type": "DEBITOR",
"vatId": null, "mark": null,
"vatCountryCode": null, "contact": {
"vatBusiness": true, "label": "first contact",
"refundBankAccount": { "holder": "Second e.K." } "emailAddresses": "contact-admin@firstcontact.example.com",
}, "phoneNumbers": "+49 123 1234567"
{ }
"debitorNumber": 1000313, },
"debitorNumberSuffix": 13, "debitorNumber": 1000111,
"partner": { "person": { "tradeName": "Third OHG" } }, "debitorNumberSuffix": 11,
"billingContact": { "label": "third contact" }, "partner": {
"vatId": null, "partnerNumber": 10001,
"vatCountryCode": null, "partnerRel": {
"vatBusiness": true, "anchor": {
"refundBankAccount": { "holder": "Third OHG" } "personType": "LEGAL_PERSON",
} "tradeName": "Hostsharing eG",
] "givenName": null,
"familyName": null
},
"holder": {
"personType": "LEGAL_PERSON",
"tradeName": "First GmbH",
"givenName": null,
"familyName": null
},
"type": "PARTNER",
"mark": null,
"contact": {
"label": "first contact",
"emailAddresses": "contact-admin@firstcontact.example.com",
"phoneNumbers": "+49 123 1234567"
}
},
"details": {
"registrationOffice": "Hamburg",
"registrationNumber": "RegNo123456789",
"birthName": null,
"birthPlace": null,
"birthday": null,
"dateOfDeath": null
}
},
"billable": true,
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true,
"vatReverseCharge": false,
"refundBankAccount": {
"holder": "First GmbH",
"iban": "DE02120300000000202051",
"bic": "BYLADEM1001"
},
"defaultPrefix": "fir"
},
{
"debitorRel": {
"anchor": {"tradeName": "Second e.K."},
"holder": {"tradeName": "Second e.K."},
"type": "DEBITOR",
"contact": {"emailAddresses": "contact-admin@secondcontact.example.com"}
},
"debitorNumber": 1000212,
"debitorNumberSuffix": 12,
"partner": {
"partnerNumber": 10002,
"partnerRel": {
"anchor": {"tradeName": "Hostsharing eG"},
"holder": {"tradeName": "Second e.K."},
"type": "PARTNER",
"contact": {"emailAddresses": "contact-admin@secondcontact.example.com"}
},
"details": {
"registrationOffice": "Hamburg",
"registrationNumber": "RegNo123456789"
}
},
"billable": true,
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true,
"vatReverseCharge": false,
"refundBankAccount": {"iban": "DE02100500000054540402"},
"defaultPrefix": "sec"
},
{
"debitorRel": {
"anchor": {"tradeName": "Third OHG"},
"holder": {"tradeName": "Third OHG"},
"type": "DEBITOR",
"contact": {"emailAddresses": "contact-admin@thirdcontact.example.com"}
},
"debitorNumber": 1000313,
"debitorNumberSuffix": 13,
"partner": {
"partnerNumber": 10003,
"partnerRel": {
"anchor": {"tradeName": "Hostsharing eG"},
"holder": {"tradeName": "Third OHG"},
"type": "PARTNER",
"contact": {"emailAddresses": "contact-admin@thirdcontact.example.com"}
},
"details": {
"registrationOffice": "Hamburg",
"registrationNumber": "RegNo123456789"
}
},
"billable": true,
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true,
"vatReverseCharge": false,
"refundBankAccount": {"iban": "DE02300209000106531065"},
"defaultPrefix": "thi"
}
]
""")); """));
// @formatter:on // @formatter:on
} }
@ -132,8 +240,10 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
[ [
{ {
"debitorNumber": 1000212, "debitorNumber": 1000212,
"partner": { "person": { "tradeName": "Second e.K." } }, "partner": { "partnerNumber": 10002 },
"billingContact": { "label": "second contact" }, "debitorRel": {
"contact": { "label": "second contact" }
},
"vatId": null, "vatId": null,
"vatCountryCode": null, "vatCountryCode": null,
"vatBusiness": true "vatBusiness": true
@ -154,6 +264,17 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Third").get(0); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Third").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0);
final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Fourth").get(0); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Fourth").get(0);
final var givenBillingPerson = personRepo.findPersonByOptionalNameLike("Fourth").get(0);
final var givenDebitorRelUUid = jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
return relRepo.save(HsOfficeRelationEntity.builder()
.type(DEBITOR)
.anchor(givenPartner.getPartnerRel().getHolder())
.holder(givenBillingPerson)
.contact(givenContact)
.build()).getUuid();
}).assertSuccessful().returnedValue();
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given() .given()
@ -161,8 +282,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"partnerUuid": "%s", "debitorRelUuid": "%s",
"billingContactUuid": "%s",
"debitorNumberSuffix": "%s", "debitorNumberSuffix": "%s",
"billable": "true", "billable": "true",
"vatId": "VAT123456", "vatId": "VAT123456",
@ -172,7 +292,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"refundBankAccountUuid": "%s", "refundBankAccountUuid": "%s",
"defaultPrefix": "for" "defaultPrefix": "for"
} }
""".formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorSuffix, givenBankAccount.getUuid())) """.formatted( givenDebitorRelUUid, ++nextDebitorSuffix, givenBankAccount.getUuid()))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/debitors") .post("http://localhost/api/hs/office/debitors")
@ -182,8 +302,8 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("vatId", is("VAT123456")) .body("vatId", is("VAT123456"))
.body("defaultPrefix", is("for")) .body("defaultPrefix", is("for"))
.body("billingContact.label", is(givenContact.getLabel())) .body("debitorRel.contact.label", is(givenContact.getLabel()))
.body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName())) .body("debitorRel.holder.tradeName", is(givenBillingPerson.getTradeName()))
.body("refundBankAccount.holder", is(givenBankAccount.getHolder())) .body("refundBankAccount.holder", is(givenBankAccount.getHolder()))
.header("Location", startsWith("http://localhost")) .header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on .extract().header("Location"); // @formatter:on
@ -206,15 +326,23 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"partnerUuid": "%s", "debitorRel": {
"billingContactUuid": "%s", "type": "DEBITOR",
"debitorNumberSuffix": "%s", "anchorUuid": "%s",
"defaultPrefix": "for", "holderUuid": "%s",
"billable": "true", "contactUuid": "%s"
"vatReverseCharge": "false" },
} "debitorNumberSuffix": "%s",
""".formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorSuffix)) "defaultPrefix": "for",
"billable": "true",
"vatReverseCharge": "false"
}
""".formatted(
givenPartner.getPartnerRel().getHolder().getUuid(),
givenPartner.getPartnerRel().getHolder().getUuid(),
givenContact.getUuid(),
++nextDebitorSuffix))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/debitors") .post("http://localhost/api/hs/office/debitors")
@ -222,8 +350,8 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.statusCode(201) .statusCode(201)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("billingContact.label", is(givenContact.getLabel())) .body("debitorRel.contact.label", is(givenContact.getLabel()))
.body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName())) .body("partner.partnerRel.holder.tradeName", is(givenPartner.getPartnerRel().getHolder().getTradeName()))
.body("vatId", equalTo(null)) .body("vatId", equalTo(null))
.body("vatCountryCode", equalTo(null)) .body("vatCountryCode", equalTo(null))
.body("vatBusiness", equalTo(false)) .body("vatBusiness", equalTo(false))
@ -250,19 +378,22 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"partnerUuid": "%s", "debitorRel": {
"billingContactUuid": "%s", "type": "DEBITOR",
"debitorNumberSuffix": "%s", "anchorUuid": "%s",
"billable": "true", "holderUuid": "%s",
"vatId": "VAT123456", "contactUuid": "%s"
"vatCountryCode": "DE", },
"vatBusiness": true, "debitorNumberSuffix": "%s",
"vatReverseCharge": "false", "defaultPrefix": "for",
"defaultPrefix": "thi" "billable": "true",
} "vatReverseCharge": "false"
""" }
.formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorSuffix)) """.formatted(
givenPartner.getPartnerRel().getAnchor().getUuid(),
givenPartner.getPartnerRel().getAnchor().getUuid(),
givenContactUuid, ++nextDebitorSuffix))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/debitors") .post("http://localhost/api/hs/office/debitors")
@ -273,10 +404,10 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
} }
@Test @Test
void globalAdmin_canNotAddDebitor_ifPartnerDoesNotExist() { void globalAdmin_canNotAddDebitor_ifDebitorRelDoesNotExist() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenPartnerUuid = UUID.fromString("00000000-0000-0000-0000-000000000000"); final var givenDebitorRelUuid = UUID.fromString("00000000-0000-0000-0000-000000000000");
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0);
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
@ -284,24 +415,20 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"partnerUuid": "%s", "debitorRelUuid": "%s",
"billingContactUuid": "%s", "debitorNumberSuffix": "%s",
"debitorNumberSuffix": "%s", "defaultPrefix": "for",
"billable": "true", "billable": "true",
"vatId": "VAT123456", "vatReverseCharge": "false"
"vatCountryCode": "DE", }
"vatBusiness": true, """.formatted(givenDebitorRelUuid, ++nextDebitorSuffix))
"vatReverseCharge": "false",
"defaultPrefix": "for"
}
""".formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorSuffix))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/debitors") .post("http://localhost/api/hs/office/debitors")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(400) .statusCode(400)
.body("message", is("Unable to find Partner with uuid 00000000-0000-0000-0000-000000000000")); .body("message", is("Unable to find HsOfficeRelationEntity with uuid 00000000-0000-0000-0000-000000000000"));
// @formatter:on // @formatter:on
} }
} }
@ -321,14 +448,53 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/office/debitors/" + givenDebitorUuid) .get("http://localhost/api/hs/office/debitors/" + givenDebitorUuid)
.then().log().body().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"partner": { person: { "tradeName": "First GmbH" } }, "debitorRel": {
"billingContact": { "label": "first contact" } "anchor": { "personType": "LEGAL_PERSON", "tradeName": "First GmbH"},
} "holder": { "personType": "LEGAL_PERSON", "tradeName": "First GmbH"},
"type": "DEBITOR",
"contact": {
"label": "first contact",
"postalAddress": "\\nVorname Nachname\\nStraße Hnr\\nPLZ Stadt\\n",
"emailAddresses": "contact-admin@firstcontact.example.com",
"phoneNumbers": "+49 123 1234567"
}
},
"debitorNumber": 1000111,
"debitorNumberSuffix": 11,
"partner": {
"partnerNumber": 10001,
"partnerRel": {
"anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG"},
"holder": { "personType": "LEGAL_PERSON", "tradeName": "First GmbH"},
"type": "PARTNER",
"mark": null,
"contact": {
"label": "first contact",
"postalAddress": "\\nVorname Nachname\\nStraße Hnr\\nPLZ Stadt\\n",
"emailAddresses": "contact-admin@firstcontact.example.com",
"phoneNumbers": "+49 123 1234567"
}
},
"details": {
"registrationOffice": "Hamburg",
"registrationNumber": "RegNo123456789"
}
},
"billable": true,
"vatBusiness": true,
"vatReverseCharge": false,
"refundBankAccount": {
"holder": "First GmbH",
"iban": "DE02120300000000202051",
"bic": "BYLADEM1001"
},
"defaultPrefix": "fir"
}
""")); // @formatter:on """)); // @formatter:on
} }
@ -350,7 +516,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
@Test @Test
@Accepts({ "Debitor:X(Access Control)" }) @Accepts({ "Debitor:X(Access Control)" })
void contactAdminUser_canGetRelatedDebitor() { void contactAdminUser_canGetRelatedDebitorExceptRefundBankAccount() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("first contact").get(0).getUuid(); final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("first contact").get(0).getUuid();
@ -365,9 +531,10 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"partner": { person: { "tradeName": "First GmbH" } }, "debitorNumber": 1000111,
"billingContact": { "label": "first contact" }, "partner": { "partnerNumber": 10001 },
"refundBankAccount": { "holder": "First GmbH" } "debitorRel": { "contact": { "label": "first contact" } },
"refundBankAccount": null
} }
""")); // @formatter:on """)); // @formatter:on
} }
@ -378,7 +545,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
class PatchDebitor { class PatchDebitor {
@Test @Test
void globalAdmin_withoutAssumedRole_canPatchAllPropertiesOfArbitraryDebitor() { void globalAdmin_withoutAssumedRole_canPatchArbitraryDebitor() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor(); final var givenDebitor = givenSomeTemporaryDebitor();
@ -400,77 +567,90 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.port(port) .port(port)
.when() .when()
.patch("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid()) .patch("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid())
.then().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("", lenientlyEquals("""
.body("vatId", is("VAT222222")) {
.body("vatCountryCode", is("AA")) "debitorRel": {
.body("vatBusiness", is(true)) "anchor": { "tradeName": "Fourth eG" },
.body("defaultPrefix", is("for")) "holder": { "tradeName": "Fourth eG" },
.body("billingContact.label", is(givenContact.getLabel())) "type": "DEBITOR",
.body("partner.person.tradeName", is(givenDebitor.getPartner().getPerson().getTradeName())); "mark": null,
"contact": { "label": "fourth contact" }
},
"debitorNumber": 10004${debitorNumberSuffix},
"debitorNumberSuffix": ${debitorNumberSuffix},
"partner": {
"partnerNumber": 10004,
"partnerRel": {
"anchor": { "tradeName": "Hostsharing eG" },
"holder": { "tradeName": "Fourth eG" },
"type": "PARTNER",
"mark": null,
"contact": { "label": "fourth contact" }
},
"details": {
"registrationOffice": "Hamburg",
"registrationNumber": "RegNo123456789",
"birthName": null,
"birthPlace": null,
"birthday": null,
"dateOfDeath": null
}
},
"billable": true,
"vatId": "VAT222222",
"vatCountryCode": "AA",
"vatBusiness": true,
"vatReverseCharge": false,
"defaultPrefix": "for"
}
"""
.replace("${debitorNumberSuffix}", givenDebitor.getDebitorNumberSuffix().toString()))
);
// @formatter:on // @formatter:on
// finally, the debitor is actually updated // finally, the debitor is actually updated
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent().get() assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent().get()
.matches(partner -> { .matches(debitor -> {
assertThat(partner.getPartner().getPerson().getTradeName()).isEqualTo(givenDebitor.getPartner() assertThat(debitor.getDebitorRel().getHolder().getTradeName())
.getPerson() .isEqualTo(givenDebitor.getDebitorRel().getHolder().getTradeName());
.getTradeName()); assertThat(debitor.getDebitorRel().getContact().getLabel()).isEqualTo("fourth contact");
assertThat(partner.getBillingContact().getLabel()).isEqualTo("fourth contact"); assertThat(debitor.getVatId()).isEqualTo("VAT222222");
assertThat(partner.getVatId()).isEqualTo("VAT222222"); assertThat(debitor.getVatCountryCode()).isEqualTo("AA");
assertThat(partner.getVatCountryCode()).isEqualTo("AA"); assertThat(debitor.isVatBusiness()).isEqualTo(true);
assertThat(partner.isVatBusiness()).isEqualTo(true);
return true; return true;
}); });
} }
@Test @Test
void globalAdmin_withoutAssumedRole_canPatchPartialPropertiesOfArbitraryDebitor() { void theContactOwner_canNotPatchARelatedDebitor() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor(); final var givenDebitor = givenSomeTemporaryDebitor();
final var newBillingContact = contactRepo.findContactByOptionalLabelLike("sixth").get(0);
final var location = RestAssured // @formatter:off // @formatter:on
.given() RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_office_contact#fourthcontact.admin")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"billingContactUuid": "%s", "vatId": "VAT999999"
"vatId": "VAT999999" }
} """)
""".formatted(newBillingContact.getUuid()))
.port(port) .port(port)
.when() .when()
.patch("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid()) .patch("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid())
.then().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(403)
.contentType(ContentType.JSON) .body("message", containsString("ERROR: [403] Subject"))
.body("uuid", isUuidValid()) .body("message", containsString("is not allowed to update hs_office_debitor uuid "));
.body("billingContact.label", is("sixth contact"))
.body("vatId", is("VAT999999"))
.body("vatCountryCode", is(givenDebitor.getVatCountryCode()))
.body("vatBusiness", is(givenDebitor.isVatBusiness()));
// @formatter:on
// finally, the debitor is actually updated
assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent().get()
.matches(partner -> {
assertThat(partner.getPartner().getPerson().getTradeName()).isEqualTo(givenDebitor.getPartner()
.getPerson()
.getTradeName());
assertThat(partner.getBillingContact().getLabel()).isEqualTo("sixth contact");
assertThat(partner.getVatId()).isEqualTo("VAT999999");
assertThat(partner.getVatCountryCode()).isEqualTo(givenDebitor.getVatCountryCode());
assertThat(partner.isVatBusiness()).isEqualTo(givenDebitor.isVatBusiness());
return true;
});
} }
} }
@Nested @Nested
@ -500,7 +680,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
void contactAdminUser_canNotDeleteRelatedDebitor() { void contactAdminUser_canNotDeleteRelatedDebitor() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor(); final var givenDebitor = givenSomeTemporaryDebitor();
assertThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("fourth contact"); assertThat(givenDebitor.getDebitorRel().getContact().getLabel()).isEqualTo("fourth contact");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -520,7 +700,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
void normalUser_canNotDeleteUnrelatedDebitor() { void normalUser_canNotDeleteUnrelatedDebitor() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor(); final var givenDebitor = givenSomeTemporaryDebitor();
assertThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("fourth contact"); assertThat(givenDebitor.getDebitorRel().getContact().getLabel()).isEqualTo("fourth contact");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -544,8 +724,14 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix(++nextDebitorSuffix) .debitorNumberSuffix(++nextDebitorSuffix)
.billable(true) .billable(true)
.partner(givenPartner) .debitorRel(
.billingContact(givenContact) HsOfficeRelationEntity.builder()
.type(DEBITOR)
.anchor(givenPartner.getPartnerRel().getHolder())
.holder(givenPartner.getPartnerRel().getHolder())
.contact(givenContact)
.build()
)
.defaultPrefix("abc") .defaultPrefix("abc")
.vatReverseCharge(false) .vatReverseCharge(false)
.build(); .build();

View File

@ -1,9 +1,8 @@
package net.hostsharing.hsadminng.hs.office.debitor; package net.hostsharing.hsadminng.hs.office.debitor;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.test.PatchUnitTestBase; import net.hostsharing.test.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
@ -28,9 +27,8 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
> { > {
private static final UUID INITIAL_DEBITOR_UUID = UUID.randomUUID(); private static final UUID INITIAL_DEBITOR_UUID = UUID.randomUUID();
private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID(); private static final UUID INITIAL_DEBITOR_REL_UUID = UUID.randomUUID();
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID(); private static final UUID PATCHED_DEBITOR_REL_UUID = UUID.randomUUID();
private static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID();
private static final String PATCHED_DEFAULT_PREFIX = "xyz"; private static final String PATCHED_DEFAULT_PREFIX = "xyz";
private static final String PATCHED_VAT_COUNTRY_CODE = "ZZ"; private static final String PATCHED_VAT_COUNTRY_CODE = "ZZ";
@ -46,12 +44,8 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
private static final UUID INITIAL_REFUND_BANK_ACCOUNT_UUID = UUID.randomUUID(); private static final UUID INITIAL_REFUND_BANK_ACCOUNT_UUID = UUID.randomUUID();
private static final UUID PATCHED_REFUND_BANK_ACCOUNT_UUID = UUID.randomUUID(); private static final UUID PATCHED_REFUND_BANK_ACCOUNT_UUID = UUID.randomUUID();
private final HsOfficePartnerEntity givenInitialPartner = HsOfficePartnerEntity.builder() private final HsOfficeRelationEntity givenInitialDebitorRel = HsOfficeRelationEntity.builder()
.uuid(INITIAL_PARTNER_UUID) .uuid(INITIAL_DEBITOR_REL_UUID)
.build();
private final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder()
.uuid(INITIAL_CONTACT_UUID)
.build(); .build();
private final HsOfficeBankAccountEntity givenInitialBankAccount = HsOfficeBankAccountEntity.builder() private final HsOfficeBankAccountEntity givenInitialBankAccount = HsOfficeBankAccountEntity.builder()
@ -62,8 +56,8 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
@BeforeEach @BeforeEach
void initMocks() { void initMocks() {
lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> lenient().when(em.getReference(eq(HsOfficeRelationEntity.class), any())).thenAnswer(invocation ->
HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficeRelationEntity.builder().uuid(invocation.getArgument(1)).build());
lenient().when(em.getReference(eq(HsOfficeBankAccountEntity.class), any())).thenAnswer(invocation -> lenient().when(em.getReference(eq(HsOfficeBankAccountEntity.class), any())).thenAnswer(invocation ->
HsOfficeBankAccountEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficeBankAccountEntity.builder().uuid(invocation.getArgument(1)).build());
} }
@ -72,8 +66,7 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
protected HsOfficeDebitorEntity newInitialEntity() { protected HsOfficeDebitorEntity newInitialEntity() {
final var entity = new HsOfficeDebitorEntity(); final var entity = new HsOfficeDebitorEntity();
entity.setUuid(INITIAL_DEBITOR_UUID); entity.setUuid(INITIAL_DEBITOR_UUID);
entity.setPartner(givenInitialPartner); entity.setDebitorRel(givenInitialDebitorRel);
entity.setBillingContact(givenInitialContact);
entity.setBillable(INITIAL_BILLABLE); entity.setBillable(INITIAL_BILLABLE);
entity.setVatId("initial VAT-ID"); entity.setVatId("initial VAT-ID");
entity.setVatCountryCode("AA"); entity.setVatCountryCode("AA");
@ -98,11 +91,11 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
protected Stream<Property> propertyTestDescriptors() { protected Stream<Property> propertyTestDescriptors() {
return Stream.of( return Stream.of(
new JsonNullableProperty<>( new JsonNullableProperty<>(
"billingContact", "debitorRel",
HsOfficeDebitorPatchResource::setBillingContactUuid, HsOfficeDebitorPatchResource::setDebitorRelUuid,
PATCHED_CONTACT_UUID, PATCHED_DEBITOR_REL_UUID,
HsOfficeDebitorEntity::setBillingContact, HsOfficeDebitorEntity::setDebitorRel,
newBillingContact(PATCHED_CONTACT_UUID)) newDebitorRel(PATCHED_DEBITOR_REL_UUID))
.notNullable(), .notNullable(),
new SimpleProperty<>( new SimpleProperty<>(
"billable", "billable",
@ -129,7 +122,7 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
new SimpleProperty<>( new SimpleProperty<>(
"vatReverseCharge", "vatReverseCharge",
HsOfficeDebitorPatchResource::setVatReverseCharge, HsOfficeDebitorPatchResource::setVatReverseCharge,
PATCHED_BILLABLE, PATCHED_VAT_REVERSE_CHARGE,
HsOfficeDebitorEntity::setVatReverseCharge) HsOfficeDebitorEntity::setVatReverseCharge)
.notNullable(), .notNullable(),
new JsonNullableProperty<>( new JsonNullableProperty<>(
@ -148,15 +141,15 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
); );
} }
private HsOfficeContactEntity newBillingContact(final UUID uuid) { private HsOfficeRelationEntity newDebitorRel(final UUID uuid) {
final var newContact = new HsOfficeContactEntity(); return HsOfficeRelationEntity.builder()
newContact.setUuid(uuid); .uuid(uuid)
return newContact; .build();
} }
private HsOfficeBankAccountEntity newBankAccount(final UUID uuid) { private HsOfficeBankAccountEntity newBankAccount(final UUID uuid) {
final var newBankAccount = new HsOfficeBankAccountEntity(); return HsOfficeBankAccountEntity.builder()
newBankAccount.setUuid(uuid); .uuid(uuid)
return newBankAccount; .build();
} }
} }

View File

@ -1,61 +1,52 @@
package net.hostsharing.hsadminng.hs.office.debitor; package net.hostsharing.hsadminng.hs.office.debitor;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsOfficeDebitorEntityUnitTest { class HsOfficeDebitorEntityUnitTest {
private HsOfficeRelationEntity givenDebitorRel = HsOfficeRelationEntity.builder()
.anchor(HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL_PERSON)
.tradeName("some partner trade name")
.build())
.holder(HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL_PERSON)
.tradeName("some billing trade name")
.build())
.contact(HsOfficeContactEntity.builder().label("some label").build())
.build();
@Test @Test
void toStringContainsPartnerAndContact() { void toStringContainsPartnerAndContact() {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix((byte)67) .debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder() .debitorRel(givenDebitorRel)
.person(HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL_PERSON)
.tradeName("some trade name")
.build())
.details(HsOfficePartnerDetailsEntity.builder().birthName("some birth name").build())
.partnerNumber(12345)
.build())
.billingContact(HsOfficeContactEntity.builder().label("some label").build())
.defaultPrefix("som") .defaultPrefix("som")
.build();
final var result = given.toString();
assertThat(result).isEqualTo("debitor(D-1234567: LP some trade name: som)");
}
@Test
void toStringWithoutPersonContainsDebitorNumber() {
final var given = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder() .partner(HsOfficePartnerEntity.builder()
.person(null)
.details(HsOfficePartnerDetailsEntity.builder().birthName("some birth name").build())
.partnerNumber(12345) .partnerNumber(12345)
.build()) .build())
.billingContact(HsOfficeContactEntity.builder().label("some label").build())
.build(); .build();
final var result = given.toString(); final var result = given.toString();
assertThat(result).isEqualTo("debitor(D-1234567: <person=null>)"); assertThat(result).isEqualTo("debitor(D-1234567: rel(anchor='LP some partner trade name', holder='LP some billing trade name'), som)");
} }
@Test @Test
void toShortStringContainsDebitorNumber() { void toShortStringContainsDebitorNumber() {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel)
.debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder() .partner(HsOfficePartnerEntity.builder()
.partnerNumber(12345) .partnerNumber(12345)
.build()) .build())
.debitorNumberSuffix((byte)67)
.build(); .build();
final var result = given.toShortString(); final var result = given.toShortString();
@ -66,10 +57,11 @@ class HsOfficeDebitorEntityUnitTest {
@Test @Test
void getDebitorNumberWithPartnerNumberAndDebitorNumberSuffix() { void getDebitorNumberWithPartnerNumberAndDebitorNumberSuffix() {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel)
.debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder() .partner(HsOfficePartnerEntity.builder()
.partnerNumber(12345) .partnerNumber(12345)
.build()) .build())
.debitorNumberSuffix((byte)67)
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getDebitorNumber();
@ -80,8 +72,9 @@ class HsOfficeDebitorEntityUnitTest {
@Test @Test
void getDebitorNumberWithoutPartnerReturnsNull() { void getDebitorNumberWithoutPartnerReturnsNull() {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.partner(null) .debitorRel(givenDebitorRel)
.debitorNumberSuffix((byte)67) .debitorNumberSuffix((byte)67)
.partner(null)
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getDebitorNumber();
@ -92,10 +85,9 @@ class HsOfficeDebitorEntityUnitTest {
@Test @Test
void getDebitorNumberWithoutPartnerNumberReturnsNull() { void getDebitorNumberWithoutPartnerNumberReturnsNull() {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.partner(HsOfficePartnerEntity.builder() .debitorRel(givenDebitorRel)
.partnerNumber(null)
.build())
.debitorNumberSuffix((byte)67) .debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder().build())
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getDebitorNumber();
@ -106,10 +98,11 @@ class HsOfficeDebitorEntityUnitTest {
@Test @Test
void getDebitorNumberWithoutDebitorNumberSuffixReturnsNull() { void getDebitorNumberWithoutDebitorNumberSuffixReturnsNull() {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel)
.debitorNumberSuffix(null)
.partner(HsOfficePartnerEntity.builder() .partner(HsOfficePartnerEntity.builder()
.partnerNumber(12345) .partnerNumber(12345)
.build()) .build())
.debitorNumberSuffix(null)
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getDebitorNumber();

View File

@ -4,11 +4,16 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.test.Array; import net.hostsharing.test.Array;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
import org.hibernate.Hibernate;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -18,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -27,13 +33,14 @@ import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import static net.hostsharing.hsadminng.hs.office.test.EntityList.one;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.test.JpaAttempt.attempt; import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest @DataJpaTest
@Import( { Context.class, JpaAttempt.class }) @Import( { Context.class, JpaAttempt.class, RbacGrantsDiagramService.class })
class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithCleanup { class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@Autowired @Autowired
@ -45,6 +52,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
@Autowired @Autowired
HsOfficeContactRepository contactRepo; HsOfficeContactRepository contactRepo;
@Autowired
HsOfficePersonRepository personRepo;
@Autowired @Autowired
HsOfficeBankAccountRepository bankAccountRepo; HsOfficeBankAccountRepository bankAccountRepo;
@ -60,9 +70,11 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
@Autowired @Autowired
JpaAttempt jpaAttempt; JpaAttempt jpaAttempt;
@Autowired
RbacGrantsDiagramService mermaidService;
@MockBean @MockBean
HttpServletRequest request; HttpServletRequest request;
@Nested @Nested
class CreateDebitor { class CreateDebitor {
@ -71,15 +83,19 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = debitorRepo.count(); final var count = debitorRepo.count();
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First GmbH").get(0); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
final var givenContact = contactRepo.findContactByOptionalLabelLike("first contact").get(0); final var givenContact = one(contactRepo.findContactByOptionalLabelLike("first contact"));
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix((byte)21) .debitorNumberSuffix((byte)21)
.partner(givenPartner) .debitorRel(HsOfficeRelationEntity.builder()
.billingContact(givenContact) .type(HsOfficeRelationType.DEBITOR)
.anchor(givenPartnerPerson)
.holder(givenPartnerPerson)
.contact(givenContact)
.build())
.defaultPrefix("abc") .defaultPrefix("abc")
.billable(false) .billable(false)
.build(); .build();
@ -99,16 +115,19 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
public void canNotCreateNewDebitorWithInvalidDefaultPrefix(final String givenPrefix) { public void canNotCreateNewDebitorWithInvalidDefaultPrefix(final String givenPrefix) {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = debitorRepo.count(); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First GmbH").get(0); final var givenContact = one(contactRepo.findContactByOptionalLabelLike("first contact"));
final var givenContact = contactRepo.findContactByOptionalLabelLike("first contact").get(0);
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix((byte)21) .debitorNumberSuffix((byte)21)
.partner(givenPartner) .debitorRel(HsOfficeRelationEntity.builder()
.billingContact(givenContact) .type(HsOfficeRelationType.DEBITOR)
.anchor(givenPartnerPerson)
.holder(givenPartnerPerson)
.contact(givenContact)
.build())
.billable(true) .billable(true)
.vatReverseCharge(false) .vatReverseCharge(false)
.vatBusiness(false) .vatBusiness(false)
@ -128,21 +147,22 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
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()).stream()
// some search+replace to make the output fit into the screen width // some search+replace to make the output fit into the screen width
.map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex"))
.map(s -> s.replace("22FourtheG-fourthcontact", "FeG"))
.map(s -> s.replace("FourtheG-fourthcontact", "FeG"))
.map(s -> s.replace("fourthcontact", "4th"))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.toList(); .toList();
// when // when
attempt(em, () -> { attempt(em, () -> {
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0); final var givenDebitorPerson = one(personRepo.findPersonByOptionalNameLike("Fourth eG"));
final var givenContact = one(contactRepo.findContactByOptionalLabelLike("fourth contact"));
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix((byte)22) .debitorNumberSuffix((byte)22)
.partner(givenPartner) .debitorRel(HsOfficeRelationEntity.builder()
.billingContact(givenContact) .type(HsOfficeRelationType.DEBITOR)
.anchor(givenPartnerPerson)
.holder(givenDebitorPerson)
.contact(givenContact)
.build())
.defaultPrefix("abc") .defaultPrefix("abc")
.billable(false) .billable(false)
.build(); .build();
@ -152,49 +172,52 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// then // then
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_office_debitor#1000422:FourtheG-fourthcontact.owner", "hs_office_relation#FirstGmbH-with-DEBITOR-FourtheG.owner",
"hs_office_debitor#1000422:FourtheG-fourthcontact.admin", "hs_office_relation#FirstGmbH-with-DEBITOR-FourtheG.admin",
"hs_office_debitor#1000422:FourtheG-fourthcontact.agent", "hs_office_relation#FirstGmbH-with-DEBITOR-FourtheG.agent",
"hs_office_debitor#1000422:FourtheG-fourthcontact.tenant", "hs_office_relation#FirstGmbH-with-DEBITOR-FourtheG.tenant"));
"hs_office_debitor#1000422:FourtheG-fourthcontact.guest"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())) assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex")) .map(s -> s.replace("hs_office_", ""))
.map(s -> s.replace("22FourtheG-fourthcontact", "FeG")) .containsExactlyInAnyOrder(Array.fromFormatted(
.map(s -> s.replace("FourtheG-fourthcontact", "FeG")) initialGrantNames,
.map(s -> s.replace("fourthcontact", "4th")) "{ grant perm INSERT into sepamandate with relation#FirstGmbH-with-DEBITOR-FourtheG to role relation#FirstGmbH-with-DEBITOR-FourtheG.admin by system and assume }",
.map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames,
// owner
"{ grant perm DELETE on debitor#1000422:FeG to role debitor#1000422:FeG.owner by system and assume }",
"{ grant role debitor#1000422:FeG.owner to role global#global.admin by system and assume }",
"{ grant role debitor#1000422:FeG.owner to user superuser-alex by global#global.admin and assume }",
// admin // owner
"{ grant perm UPDATE on debitor#1000422:FeG to role debitor#1000422:FeG.admin by system and assume }", "{ grant perm DELETE on debitor#D-1000122 to role relation#FirstGmbH-with-DEBITOR-FourtheG.owner by system and assume }",
"{ grant role debitor#1000422:FeG.admin to role debitor#1000422:FeG.owner by system and assume }", "{ grant perm DELETE on relation#FirstGmbH-with-DEBITOR-FourtheG to role relation#FirstGmbH-with-DEBITOR-FourtheG.owner by system and assume }",
"{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.owner to role global#global.admin by system and assume }",
"{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.owner to user superuser-alex@hostsharing.net by relation#FirstGmbH-with-DEBITOR-FourtheG.owner and assume }",
// agent // admin
"{ grant role debitor#1000422:FeG.agent to role debitor#1000422:FeG.admin by system and assume }", "{ grant perm UPDATE on debitor#D-1000122 to role relation#FirstGmbH-with-DEBITOR-FourtheG.admin by system and assume }",
"{ grant role debitor#1000422:FeG.agent to role contact#4th.admin by system and assume }", "{ grant perm UPDATE on relation#FirstGmbH-with-DEBITOR-FourtheG to role relation#FirstGmbH-with-DEBITOR-FourtheG.admin by system and assume }",
"{ grant role debitor#1000422:FeG.agent to role partner#10004:FeG.admin by system and assume }", "{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.admin to role relation#FirstGmbH-with-DEBITOR-FourtheG.owner by system and assume }",
"{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.admin to role person#FirstGmbH.admin by system and assume }",
"{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.admin to role relation#HostsharingeG-with-PARTNER-FirstGmbH.admin by system and assume }",
// tenant // agent
"{ grant role contact#4th.guest to role debitor#1000422:FeG.tenant by system and assume }", "{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.agent to role person#FourtheG.admin by system and assume }",
"{ grant role debitor#1000422:FeG.tenant to role debitor#1000422:FeG.agent by system and assume }", "{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.agent to role relation#FirstGmbH-with-DEBITOR-FourtheG.admin by system and assume }",
"{ grant role debitor#1000422:FeG.tenant to role partner#10004:FeG.agent by system and assume }", "{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.agent to role relation#HostsharingeG-with-PARTNER-FirstGmbH.agent by system and assume }",
"{ grant role partner#10004:FeG.tenant to role debitor#1000422:FeG.tenant by system and assume }",
// guest // tenant
"{ grant perm SELECT on debitor#1000422:FeG to role debitor#1000422:FeG.guest by system and assume }", "{ grant perm SELECT on debitor#D-1000122 to role relation#FirstGmbH-with-DEBITOR-FourtheG.tenant by system and assume }",
"{ grant role debitor#1000422:FeG.guest to role debitor#1000422:FeG.tenant by system and assume }", "{ grant perm SELECT on relation#FirstGmbH-with-DEBITOR-FourtheG to role relation#FirstGmbH-with-DEBITOR-FourtheG.tenant by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-FirstGmbH.tenant to role relation#FirstGmbH-with-DEBITOR-FourtheG.agent by system and assume }",
"{ grant role contact#fourthcontact.referrer to role relation#FirstGmbH-with-DEBITOR-FourtheG.tenant by system and assume }",
"{ grant role person#FirstGmbH.referrer to role relation#FirstGmbH-with-DEBITOR-FourtheG.tenant by system and assume }",
"{ grant role person#FourtheG.referrer to role relation#FirstGmbH-with-DEBITOR-FourtheG.tenant by system and assume }",
"{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.tenant to role contact#fourthcontact.admin by system and assume }",
"{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.tenant to role person#FourtheG.admin by system and assume }",
"{ grant role relation#FirstGmbH-with-DEBITOR-FourtheG.tenant to role relation#FirstGmbH-with-DEBITOR-FourtheG.agent by system and assume }",
null)); null));
} }
private void assertThatDebitorIsPersisted(final HsOfficeDebitorEntity saved) { private void assertThatDebitorIsPersisted(final HsOfficeDebitorEntity saved) {
final var savedRefreshed = refresh(saved);
final var found = debitorRepo.findByUuid(saved.getUuid()); final var found = debitorRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved); assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(savedRefreshed);
} }
} }
@ -212,9 +235,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// then // then
allTheseDebitorsAreReturned( allTheseDebitorsAreReturned(
result, result,
"debitor(D-1000111: LP First GmbH: fir)", "debitor(D-1000111: rel(anchor='LP First GmbH', type='DEBITOR', holder='LP First GmbH'), fir)",
"debitor(D-1000212: LP Second e.K.: sec)", "debitor(D-1000212: rel(anchor='LP Second e.K.', type='DEBITOR', holder='LP Second e.K.'), sec)",
"debitor(D-1000313: IF Third OHG: thi)"); "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
} }
@ParameterizedTest @ParameterizedTest
@ -233,8 +256,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// then: // then:
exactlyTheseDebitorsAreReturned(result, exactlyTheseDebitorsAreReturned(result,
"debitor(D-1000111: LP First GmbH: fir)", "debitor(D-1000111: P-10001, fir)",
"debitor(D-1000120: LP First GmbH: fif)"); "debitor(D-1000120: P-10001, fif)");
} }
@Test @Test
@ -262,7 +285,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
final var result = debitorRepo.findDebitorByDebitorNumber(1000313); final var result = debitorRepo.findDebitorByDebitorNumber(1000313);
// then // then
exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: IF Third OHG: thi)"); exactlyTheseDebitorsAreReturned(result,
"debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
} }
} }
@ -278,7 +302,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
final var result = debitorRepo.findDebitorByOptionalNameLike("third contact"); final var result = debitorRepo.findDebitorByOptionalNameLike("third contact");
// then // then
exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: IF Third OHG: thi)"); exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
} }
} }
@ -290,13 +314,14 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#10004:FourtheG-fourthcontact.admin"); "hs_office_relation#FourtheG-with-DEBITOR-FourtheG.admin", true);
assertThatDebitorActuallyInDatabase(givenDebitor); final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First"));
final var givenNewPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); final var givenNewBillingPerson = one(personRepo.findPersonByOptionalNameLike("Firby"));
final var givenNewContact = contactRepo.findContactByOptionalLabelLike("sixth contact").get(0); final var givenNewContact = one(contactRepo.findContactByOptionalLabelLike("sixth contact"));
final var givenNewBankAccount = bankAccountRepo.findByOptionalHolderLike("first").get(0); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first"));
final String givenNewVatId = "NEW-VAT-ID"; final String givenNewVatId = "NEW-VAT-ID";
final String givenNewVatCountryCode = "NC"; final String givenNewVatCountryCode = "NC";
final boolean givenNewVatBusiness = !givenDebitor.isVatBusiness(); final boolean givenNewVatBusiness = !givenDebitor.isVatBusiness();
@ -304,8 +329,12 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
givenDebitor.setPartner(givenNewPartner); givenDebitor.setDebitorRel(HsOfficeRelationEntity.builder()
givenDebitor.setBillingContact(givenNewContact); .type(HsOfficeRelationType.DEBITOR)
.anchor(givenNewPartnerPerson)
.holder(givenNewBillingPerson)
.contact(givenNewContact)
.build());
givenDebitor.setRefundBankAccount(givenNewBankAccount); givenDebitor.setRefundBankAccount(givenNewBankAccount);
givenDebitor.setVatId(givenNewVatId); givenDebitor.setVatId(givenNewVatId);
givenDebitor.setVatCountryCode(givenNewVatCountryCode); givenDebitor.setVatCountryCode(givenNewVatCountryCode);
@ -317,15 +346,15 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
result.assertSuccessful(); result.assertSuccessful();
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"global#global.admin"); "global#global.admin", true);
// ... partner role was reassigned: // ... partner role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_partner#10004:FourtheG-fourthcontact.agent"); "hs_office_relation#FourtheG-with-DEBITOR-FourtheG.admin");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_partner#10001:FirstGmbH-firstcontact.agent"); "hs_office_relation#FirstGmbH-with-DEBITOR-FirbySusan.agent", true);
// ... contact role was reassigned: // ... contact role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
@ -333,15 +362,15 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
"hs_office_contact#fifthcontact.admin"); "hs_office_contact#fifthcontact.admin");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_contact#sixthcontact.admin"); "hs_office_contact#sixthcontact.admin", false);
// ... bank-account role was reassigned: // ... bank-account role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_bankaccount#FourtheG.admin"); "hs_office_bankaccount#DE02200505501015871393.admin");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_bankaccount#FirstGmbH.admin"); "hs_office_bankaccount#DE02120300000000202051.admin", true);
} }
@Test @Test
@ -351,9 +380,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null, "fig"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null, "fig");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#10004:FourtheG-fourthcontact.admin"); "hs_office_relation#FourtheG-with-DEBITOR-FourtheG.admin", true);
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor, true);
final var givenNewBankAccount = bankAccountRepo.findByOptionalHolderLike("first").get(0); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first"));
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -366,12 +395,12 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
result.assertSuccessful(); result.assertSuccessful();
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"global#global.admin"); "global#global.admin", true);
// ... bank-account role was assigned: // ... bank-account role was assigned:
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_bankaccount#FirstGmbH.admin"); "hs_office_bankaccount#DE02120300000000202051.admin", true);
} }
@Test @Test
@ -381,8 +410,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fih"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fih");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#10004:FourtheG-fourthcontact.admin"); "hs_office_relation#HostsharingeG-with-PARTNER-FourtheG.agent", true);
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor, true);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -395,34 +424,34 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
result.assertSuccessful(); result.assertSuccessful();
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"global#global.admin"); "global#global.admin", true);
// ... bank-account role was removed from previous bank-account admin: // ... bank-account role was removed from previous bank-account admin:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_bankaccount#FourtheG.admin"); "hs_office_bankaccount#DE02200505501015871393.admin");
} }
@Test @Test
public void partnerAdmin_canNotUpdateRelatedDebitor() { public void partnerAgent_canNotUpdateRelatedDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth", "eig"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth", "eig");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#10004:FourtheG-fourthcontact.admin"); "hs_office_relation#HostsharingeG-with-PARTNER-FourtheG.agent", true);
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor, true);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_partner#10004:FourtheG-fourthcontact.admin"); context("superuser-alex@hostsharing.net", "hs_office_relation#HostsharingeG-with-PARTNER-FourtheG.agent");
givenDebitor.setVatId("NEW-VAT-ID"); givenDebitor.setVatId("NEW-VAT-ID");
return toCleanup(debitorRepo.save(givenDebitor)); return toCleanup(debitorRepo.save(givenDebitor));
}); });
// then // then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class, result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"[403] Subject ", " is not allowed to update hs_office_debitor uuid"); "[403] Subject ", " is not allowed to update hs_office_debitor uuid");
} }
@Test @Test
@ -430,10 +459,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "ninth", "Fourth", "nin"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "ninth", "Fourth", "nin");
assertThatDebitorActuallyInDatabase(givenDebitor, true);
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_contact#ninthcontact.admin"); "hs_office_contact#ninthcontact.admin", false);
assertThatDebitorActuallyInDatabase(givenDebitor);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -443,22 +472,34 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
}); });
// then // then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class, result.assertExceptionWithRootCauseMessage(
"[403] Subject ", " is not allowed to update hs_office_debitor uuid"); JpaObjectRetrievalFailureException.class,
// this technical error message gets translated to a [403] error at the controller level
"Unable to find net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity with id ");
} }
private void assertThatDebitorActuallyInDatabase(final HsOfficeDebitorEntity saved) { private void assertThatDebitorActuallyInDatabase(final HsOfficeDebitorEntity saved, final boolean withPartner) {
final var found = debitorRepo.findByUuid(saved.getUuid()); final var found = debitorRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().isNotSameAs(saved) assertThat(found).isNotEmpty();
.extracting(Object::toString).isEqualTo(saved.toString()); found.ifPresent(foundEntity -> {
em.refresh(foundEntity);
Hibernate.initialize(foundEntity);
assertThat(foundEntity).isNotSameAs(saved);
if (withPartner) {
assertThat(foundEntity.getPartner()).isNotNull();
}
assertThat(foundEntity.getDebitorRel()).extracting(HsOfficeRelationEntity::toString)
.isEqualTo(saved.getDebitorRel().toString());
});
} }
private void assertThatDebitorIsVisibleForUserWithRole( private void assertThatDebitorIsVisibleForUserWithRole(
final HsOfficeDebitorEntity entity, final HsOfficeDebitorEntity entity,
final String assumedRoles) { final String assumedRoles,
final boolean withPartner) {
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", assumedRoles); context("superuser-alex@hostsharing.net", assumedRoles);
assertThatDebitorActuallyInDatabase(entity); assertThatDebitorActuallyInDatabase(entity, withPartner);
}).assertSuccessful(); }).assertSuccessful();
} }
@ -497,14 +538,14 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
} }
@Test @Test
public void relatedPerson_canNotDeleteTheirRelatedDebitor() { public void debitorAgent_canViewButNotDeleteTheirRelatedDebitor() {
// given // given
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eleventh", "Fourth", "ele"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eleventh", "Fourth", "ele");
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("person-FourtheG@example.com"); context("superuser-alex@hostsharing.net", "hs_office_relation#FourtheG-with-DEBITOR-FourtheG.admin");
assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent(); assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent();
debitorRepo.deleteByUuid(givenDebitor.getUuid()); debitorRepo.deleteByUuid(givenDebitor.getUuid());
@ -561,20 +602,24 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
} }
private HsOfficeDebitorEntity givenSomeTemporaryDebitor( private HsOfficeDebitorEntity givenSomeTemporaryDebitor(
final String partner, final String partnerName,
final String contact, final String contactLabel,
final String bankAccount, final String bankAccountHolder,
final String defaultPrefix) { final String defaultPrefix) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partner).get(0); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike(partnerName));
final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0); final var givenContact = one(contactRepo.findContactByOptionalLabelLike(contactLabel));
final var givenBankAccount = final var givenBankAccount =
bankAccount != null ? bankAccountRepo.findByOptionalHolderLike(bankAccount).get(0) : null; bankAccountHolder != null ? one(bankAccountRepo.findByOptionalHolderLike(bankAccountHolder)) : null;
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix((byte)20) .debitorNumberSuffix((byte)20)
.partner(givenPartner) .debitorRel(HsOfficeRelationEntity.builder()
.billingContact(givenContact) .type(HsOfficeRelationType.DEBITOR)
.anchor(givenPartnerPerson)
.holder(givenPartnerPerson)
.contact(givenContact)
.build())
.refundBankAccount(givenBankAccount) .refundBankAccount(givenBankAccount)
.defaultPrefix(defaultPrefix) .defaultPrefix(defaultPrefix)
.billable(true) .billable(true)

View File

@ -1,7 +1,8 @@
package net.hostsharing.hsadminng.hs.office.debitor; package net.hostsharing.hsadminng.hs.office.debitor;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.TEST_CONTACT; import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.TEST_CONTACT;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
@ -13,7 +14,11 @@ public class TestHsOfficeDebitor {
public static final HsOfficeDebitorEntity TEST_DEBITOR = HsOfficeDebitorEntity.builder() public static final HsOfficeDebitorEntity TEST_DEBITOR = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix(DEFAULT_DEBITOR_SUFFIX) .debitorNumberSuffix(DEFAULT_DEBITOR_SUFFIX)
.debitorRel(HsOfficeRelationEntity.builder()
.holder(HsOfficePersonEntity.builder().build())
.anchor(HsOfficePersonEntity.builder().build())
.contact(TEST_CONTACT)
.build())
.partner(TEST_PARTNER) .partner(TEST_PARTNER)
.billingContact(TEST_CONTACT)
.build(); .build();
} }

View File

@ -5,7 +5,6 @@ import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.test.Accepts; import net.hostsharing.test.Accepts;
@ -24,6 +23,7 @@ import jakarta.persistence.PersistenceContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeReasonForTermination.CANCELLATION;
import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeReasonForTermination.NONE; import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeReasonForTermination.NONE;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
@ -51,9 +51,6 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
@Autowired @Autowired
HsOfficeMembershipRepository membershipRepo; HsOfficeMembershipRepository membershipRepo;
@Autowired
HsOfficeDebitorRepository debitorRepo;
@Autowired @Autowired
HsOfficePartnerRepository partnerRepo; HsOfficePartnerRepository partnerRepo;
@ -82,8 +79,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"partner": { "person": { "tradeName": "First GmbH" } }, "partner": { "partnerNumber": 10001 },
"mainDebitor": { "debitorNumber": 1000111 },
"memberNumber": 1000101, "memberNumber": 1000101,
"memberNumberSuffix": "01", "memberNumberSuffix": "01",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
@ -91,8 +87,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
"reasonForTermination": "NONE" "reasonForTermination": "NONE"
}, },
{ {
"partner": { "person": { "tradeName": "Second e.K." } }, "partner": { "partnerNumber": 10002 },
"mainDebitor": { "debitorNumber": 1000212 },
"memberNumber": 1000202, "memberNumber": 1000202,
"memberNumberSuffix": "02", "memberNumberSuffix": "02",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
@ -100,8 +95,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
"reasonForTermination": "NONE" "reasonForTermination": "NONE"
}, },
{ {
"partner": { "person": { "tradeName": "Third OHG" } }, "partner": { "partnerNumber": 10003 },
"mainDebitor": { "debitorNumber": 1000313 },
"memberNumber": 1000303, "memberNumber": 1000303,
"memberNumberSuffix": "03", "memberNumberSuffix": "03",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
@ -132,8 +126,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"partner": { "person": { "tradeName": "First GmbH" } }, "partner": { "partnerNumber": 10001 },
"mainDebitor": { "debitorNumber": 1000111 },
"memberNumber": 1000101, "memberNumber": 1000101,
"memberNumberSuffix": "01", "memberNumberSuffix": "01",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
@ -161,8 +154,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"partner": { "person": { "tradeName": "Second e.K." } }, "partner": { "partnerNumber": 10002 },
"mainDebitor": { "debitorNumber": 1000212 },
"memberNumber": 1000202, "memberNumber": 1000202,
"memberNumberSuffix": "02", "memberNumberSuffix": "02",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
@ -184,7 +176,6 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Third").get(0); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Third").get(0);
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("Third").get(0);
final var givenMemberSuffix = TEMP_MEMBER_NUMBER_SUFFIX; final var givenMemberSuffix = TEMP_MEMBER_NUMBER_SUFFIX;
final var expectedMemberNumber = Integer.parseInt(givenPartner.getPartnerNumber() + TEMP_MEMBER_NUMBER_SUFFIX); final var expectedMemberNumber = Integer.parseInt(givenPartner.getPartnerNumber() + TEMP_MEMBER_NUMBER_SUFFIX);
@ -195,12 +186,11 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.body(""" .body("""
{ {
"partnerUuid": "%s", "partnerUuid": "%s",
"mainDebitorUuid": "%s",
"memberNumberSuffix": "%s", "memberNumberSuffix": "%s",
"validFrom": "2022-10-13", "validFrom": "2022-10-13",
"membershipFeeBillable": "true" "membershipFeeBillable": "true"
} }
""".formatted(givenPartner.getUuid(), givenDebitor.getUuid(), givenMemberSuffix)) """.formatted(givenPartner.getUuid(), givenMemberSuffix))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/memberships") .post("http://localhost/api/hs/office/memberships")
@ -208,9 +198,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.statusCode(201) .statusCode(201)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("mainDebitor.debitorNumber", is(givenDebitor.getDebitorNumber())) .body("partner.partnerNumber", is(10003))
.body("mainDebitor.debitorNumberSuffix", is((int) givenDebitor.getDebitorNumberSuffix()))
.body("partner.person.tradeName", is("Third OHG"))
.body("memberNumber", is(expectedMemberNumber)) .body("memberNumber", is(expectedMemberNumber))
.body("memberNumberSuffix", is(givenMemberSuffix)) .body("memberNumberSuffix", is(givenMemberSuffix))
.body("validFrom", is("2022-10-13")) .body("validFrom", is("2022-10-13"))
@ -246,8 +234,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"partner": { "person": { "tradeName": "First GmbH" } }, "partner": { "partnerNumber": 10001 },
"mainDebitor": { "debitorNumber": 1000111 },
"memberNumber": 1000101, "memberNumber": 1000101,
"memberNumberSuffix": "01", "memberNumberSuffix": "01",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
@ -275,14 +262,14 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
@Test @Test
@Accepts({ "Membership:X(Access Control)" }) @Accepts({ "Membership:X(Access Control)" })
void debitorAgentUser_canGetRelatedMembership() { void parnerRelAgent_canGetRelatedMembership() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000303).getUuid(); final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000303).getUuid();
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_office_debitor#1000313:ThirdOHG-thirdcontact.agent") .header("assumed-roles", "hs_office_relation#HostsharingeG-with-PARTNER-ThirdOHG.agent")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/office/memberships/" + givenMembershipUuid) .get("http://localhost/api/hs/office/memberships/" + givenMembershipUuid)
@ -291,11 +278,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"partner": { "person": { "tradeName": "Third OHG" } }, "partner": { "partnerNumber": 10003 },
"mainDebitor": {
"debitorNumber": 1000313,
"billingContact": { "label": "third contact" }
},
"memberNumber": 1000303, "memberNumber": 1000303,
"memberNumberSuffix": "03", "memberNumberSuffix": "03",
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
@ -314,7 +297,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
void globalAdmin_canPatchValidToOfArbitraryMembership() { void globalAdmin_canPatchValidToOfArbitraryMembership() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembershipBessler(); final var givenMembership = givenSomeTemporaryMembershipBessler("First");
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given() .given()
@ -333,10 +316,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.statusCode(200) .statusCode(200)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName())) .body("partner.partnerNumber", is(givenMembership.getPartner().getPartnerNumber()))
.body("mainDebitor.debitorNumber", is(givenMembership.getMainDebitor().getDebitorNumber()))
.body("mainDebitor.debitorNumberSuffix", is((int) givenMembership.getMainDebitor().getDebitorNumberSuffix()))
.body("mainDebitor.debitorNumberSuffix", is((int) givenMembership.getMainDebitor().getDebitorNumberSuffix()))
.body("memberNumberSuffix", is(givenMembership.getMemberNumberSuffix())) .body("memberNumberSuffix", is(givenMembership.getMemberNumberSuffix()))
.body("validFrom", is("2022-11-01")) .body("validFrom", is("2022-11-01"))
.body("validTo", is("2023-12-31")) .body("validTo", is("2023-12-31"))
@ -346,72 +326,31 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
// finally, the Membership is actually updated // finally, the Membership is actually updated
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get()
.matches(mandate -> { .matches(mandate -> {
assertThat(mandate.getPartner().toShortString()).isEqualTo("LP First GmbH"); assertThat(mandate.getPartner().toShortString()).isEqualTo("P-10001");
assertThat(mandate.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString());
assertThat(mandate.getMemberNumberSuffix()).isEqualTo(givenMembership.getMemberNumberSuffix()); assertThat(mandate.getMemberNumberSuffix()).isEqualTo(givenMembership.getMemberNumberSuffix());
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-01)"); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-01)");
assertThat(mandate.getReasonForTermination()).isEqualTo(HsOfficeReasonForTermination.CANCELLATION); assertThat(mandate.getReasonForTermination()).isEqualTo(CANCELLATION);
return true; return true;
}); });
} }
@Test @Test
void globalAdmin_canPatchMainDebitorOfArbitraryMembership() { void partnerRelAgent_canPatchValidityOfRelatedMembership() {
context.define("superuser-alex@hostsharing.net"); // given
final var givenMembership = givenSomeTemporaryMembershipBessler(); final var givenPartnerAgent = "hs_office_relation#HostsharingeG-with-PARTNER-FirstGmbH.agent";
final var givenNewMainDebitor = debitorRepo.findDebitorByDebitorNumber(1000313).get(0); context.define("superuser-alex@hostsharing.net", givenPartnerAgent);
final var givenMembership = givenSomeTemporaryMembershipBessler("First");
// when
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", givenPartnerAgent)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"mainDebitorUuid": "%s" "validTo": "2024-01-01",
}
""".formatted(givenNewMainDebitor.getUuid()))
.port(port)
.when()
.patch("http://localhost/api/hs/office/memberships/" + givenMembership.getUuid())
.then().log().all().assertThat()
.statusCode(200)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName()))
.body("mainDebitor.debitorNumber", is(1000313))
.body("memberNumberSuffix", is(givenMembership.getMemberNumberSuffix()))
.body("validFrom", is("2022-11-01"))
.body("validTo", nullValue())
.body("reasonForTermination", is("NONE"));
// @formatter:on
// finally, the Membership is actually updated
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get()
.matches(mandate -> {
assertThat(mandate.getPartner().toShortString()).isEqualTo("LP First GmbH");
assertThat(mandate.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString());
assertThat(mandate.getMemberNumberSuffix()).isEqualTo(givenMembership.getMemberNumberSuffix());
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,)");
assertThat(mandate.getReasonForTermination()).isEqualTo(NONE);
return true;
});
}
@Test
void partnerAgent_canViewButNotPatchValidityOfRelatedMembership() {
context.define("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.agent");
final var givenMembership = givenSomeTemporaryMembershipBessler();
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_office_partner#10001:FirstGmbH-firstcontact.agent")
.contentType(ContentType.JSON)
.body("""
{
"validTo": "2023-12-31",
"reasonForTermination": "CANCELLATION" "reasonForTermination": "CANCELLATION"
} }
""") """)
@ -419,13 +358,13 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.when() .when()
.patch("http://localhost/api/hs/office/memberships/" + givenMembership.getUuid()) .patch("http://localhost/api/hs/office/memberships/" + givenMembership.getUuid())
.then().assertThat() .then().assertThat()
.statusCode(403); // @formatter:on .statusCode(200); // @formatter:on
// finally, the Membership is actually updated // finally, the Membership is actually updated
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get()
.matches(mandate -> { .matches(mandate -> {
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,)"); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-02)");
assertThat(mandate.getReasonForTermination()).isEqualTo(NONE); assertThat(mandate.getReasonForTermination()).isEqualTo(CANCELLATION);
return true; return true;
}); });
} }
@ -438,7 +377,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
@Test @Test
void globalAdmin_canDeleteArbitraryMembership() { void globalAdmin_canDeleteArbitraryMembership() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembershipBessler(); final var givenMembership = givenSomeTemporaryMembershipBessler("First");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -457,12 +396,12 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
@Accepts({ "Membership:X(Access Control)" }) @Accepts({ "Membership:X(Access Control)" })
void partnerAgentUser_canNotDeleteRelatedMembership() { void partnerAgentUser_canNotDeleteRelatedMembership() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembershipBessler(); final var givenMembership = givenSomeTemporaryMembershipBessler("First");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_office_partner#10001:FirstGmbH-firstcontact.admin") .header("assumed-roles", "hs_office_relation#HostsharingeG-with-PARTNER-FirstGmbH.agent")
.port(port) .port(port)
.when() .when()
.delete("http://localhost/api/hs/office/memberships/" + givenMembership.getUuid()) .delete("http://localhost/api/hs/office/memberships/" + givenMembership.getUuid())
@ -477,7 +416,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
@Accepts({ "Membership:X(Access Control)" }) @Accepts({ "Membership:X(Access Control)" })
void normalUser_canNotDeleteUnrelatedMembership() { void normalUser_canNotDeleteUnrelatedMembership() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembershipBessler(); final var givenMembership = givenSomeTemporaryMembershipBessler("First");
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -493,15 +432,13 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
} }
} }
private HsOfficeMembershipEntity givenSomeTemporaryMembershipBessler() { private HsOfficeMembershipEntity givenSomeTemporaryMembershipBessler(final String partnerName) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerName).get(0);
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
final var newMembership = HsOfficeMembershipEntity.builder() final var newMembership = HsOfficeMembershipEntity.builder()
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.partner(givenPartner) .partner(givenPartner)
.mainDebitor(givenDebitor)
.memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX) .memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX)
.validity(Range.closedInfinite(LocalDate.parse("2022-11-01"))) .validity(Range.closedInfinite(LocalDate.parse("2022-11-01")))
.reasonForTermination(NONE) .reasonForTermination(NONE)

View File

@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.hs.office.membership;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository; import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -28,7 +27,6 @@ import java.util.UUID;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -76,7 +74,6 @@ public class HsOfficeMembershipControllerRestTest {
.content(""" .content("""
{ {
"partnerUuid": null, "partnerUuid": null,
"mainDebitorUuid": "%s",
"memberNumberSuffix": "01", "memberNumberSuffix": "01",
"validFrom": "2022-10-13", "validFrom": "2022-10-13",
"membershipFeeBillable": "true" "membershipFeeBillable": "true"
@ -91,40 +88,12 @@ public class HsOfficeMembershipControllerRestTest {
.andExpect(jsonPath("message", is("[partnerUuid must not be null but is \"null\"]"))); .andExpect(jsonPath("message", is("[partnerUuid must not be null but is \"null\"]")));
} }
@Test
void respondBadRequest_ifDebitorUuidIsMissing() throws Exception {
// when
mockMvc.perform(MockMvcRequestBuilders
.post("/api/hs/office/memberships")
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"partnerUuid": "%s",
"mainDebitorUuid": null,
"memberNumberSuffix": "01",
"validFrom": "2022-10-13",
"membershipFeeBillable": "true"
}
""".formatted(UUID.randomUUID()))
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("statusCode", is(400)))
.andExpect(jsonPath("statusPhrase", is("Bad Request")))
.andExpect(jsonPath("message", is("[mainDebitorUuid must not be null but is \"null\"]")));
}
@Test @Test
void respondBadRequest_ifAnyGivenPartnerUuidCannotBeFound() throws Exception { void respondBadRequest_ifAnyGivenPartnerUuidCannotBeFound() throws Exception {
// given // given
final var givenPartnerUuid = UUID.randomUUID(); final var givenPartnerUuid = UUID.randomUUID();
final var givenMainDebitorUuid = UUID.randomUUID();
when(em.find(HsOfficePartnerEntity.class, givenPartnerUuid)).thenReturn(null); when(em.find(HsOfficePartnerEntity.class, givenPartnerUuid)).thenReturn(null);
when(em.find(HsOfficeDebitorEntity.class, givenMainDebitorUuid)).thenReturn(mock(HsOfficeDebitorEntity.class));
// when // when
mockMvc.perform(MockMvcRequestBuilders mockMvc.perform(MockMvcRequestBuilders
@ -134,12 +103,11 @@ public class HsOfficeMembershipControllerRestTest {
.content(""" .content("""
{ {
"partnerUuid": "%s", "partnerUuid": "%s",
"mainDebitorUuid": "%s",
"memberNumberSuffix": "01", "memberNumberSuffix": "01",
"validFrom": "2022-10-13", "validFrom": "2022-10-13",
"membershipFeeBillable": "true" "membershipFeeBillable": "true"
} }
""".formatted(givenPartnerUuid, givenMainDebitorUuid)) """.formatted(givenPartnerUuid))
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
// then // then
@ -149,38 +117,6 @@ public class HsOfficeMembershipControllerRestTest {
.andExpect(jsonPath("message", is("Unable to find Partner with uuid " + givenPartnerUuid))); .andExpect(jsonPath("message", is("Unable to find Partner with uuid " + givenPartnerUuid)));
} }
@Test
void respondBadRequest_ifAnyGivenDebitorUuidCannotBeFound() throws Exception {
// given
final var givenPartnerUuid = UUID.randomUUID();
final var givenMainDebitorUuid = UUID.randomUUID();
when(em.find(HsOfficePartnerEntity.class, givenPartnerUuid)).thenReturn(mock(HsOfficePartnerEntity.class));
when(em.find(HsOfficeDebitorEntity.class, givenMainDebitorUuid)).thenReturn(null);
// when
mockMvc.perform(MockMvcRequestBuilders
.post("/api/hs/office/memberships")
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"partnerUuid": "%s",
"mainDebitorUuid": "%s",
"memberNumberSuffix": "01",
"validFrom": "2022-10-13",
"membershipFeeBillable": "true"
}
""".formatted(givenPartnerUuid, givenMainDebitorUuid))
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("statusCode", is(400)))
.andExpect(jsonPath("statusPhrase", is("Bad Request")))
.andExpect(jsonPath("message", is("Unable to find Debitor with uuid " + givenMainDebitorUuid)));
}
@ParameterizedTest @ParameterizedTest
@EnumSource(InvalidMemberSuffixVariants.class) @EnumSource(InvalidMemberSuffixVariants.class)
void respondBadRequest_ifMemberNumberSuffixIsInvalid(final InvalidMemberSuffixVariants testCase) throws Exception { void respondBadRequest_ifMemberNumberSuffixIsInvalid(final InvalidMemberSuffixVariants testCase) throws Exception {
@ -193,12 +129,11 @@ public class HsOfficeMembershipControllerRestTest {
.content(""" .content("""
{ {
"partnerUuid": "%s", "partnerUuid": "%s",
"mainDebitorUuid": "%s",
%s %s
"validFrom": "2022-10-13", "validFrom": "2022-10-13",
"membershipFeeBillable": "true" "membershipFeeBillable": "true"
} }
""".formatted(UUID.randomUUID(), UUID.randomUUID(), testCase.memberNumberSuffixEntry)) """.formatted(UUID.randomUUID(), testCase.memberNumberSuffixEntry))
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
// then // then

View File

@ -17,7 +17,6 @@ import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@ -32,7 +31,6 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
> { > {
private static final UUID INITIAL_MEMBERSHIP_UUID = UUID.randomUUID(); private static final UUID INITIAL_MEMBERSHIP_UUID = UUID.randomUUID();
private static final UUID PATCHED_MAIN_DEBITOR_UUID = UUID.randomUUID();
private static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-04-15"); 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 LocalDate PATCHED_VALID_TO = LocalDate.parse("2022-12-31");
@ -56,7 +54,6 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
protected HsOfficeMembershipEntity newInitialEntity() { protected HsOfficeMembershipEntity newInitialEntity() {
final var entity = new HsOfficeMembershipEntity(); final var entity = new HsOfficeMembershipEntity();
entity.setUuid(INITIAL_MEMBERSHIP_UUID); entity.setUuid(INITIAL_MEMBERSHIP_UUID);
entity.setMainDebitor(TEST_DEBITOR);
entity.setPartner(TEST_PARTNER); entity.setPartner(TEST_PARTNER);
entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM)); entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM));
entity.setMembershipFeeBillable(GIVEN_MEMBERSHIP_FEE_BILLABLE); entity.setMembershipFeeBillable(GIVEN_MEMBERSHIP_FEE_BILLABLE);
@ -70,19 +67,12 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
@Override @Override
protected HsOfficeMembershipEntityPatcher createPatcher(final HsOfficeMembershipEntity membership) { protected HsOfficeMembershipEntityPatcher createPatcher(final HsOfficeMembershipEntity membership) {
return new HsOfficeMembershipEntityPatcher(em, mapper, membership); return new HsOfficeMembershipEntityPatcher(mapper, membership);
} }
@Override @Override
protected Stream<Property> propertyTestDescriptors() { protected Stream<Property> propertyTestDescriptors() {
return Stream.of( return Stream.of(
new JsonNullableProperty<>(
"debitor",
HsOfficeMembershipPatchResource::setMainDebitorUuid,
PATCHED_MAIN_DEBITOR_UUID,
HsOfficeMembershipEntity::setMainDebitor,
newDebitor(PATCHED_MAIN_DEBITOR_UUID))
.notNullable(),
new JsonNullableProperty<>( new JsonNullableProperty<>(
"valid", "valid",
HsOfficeMembershipPatchResource::setValidTo, HsOfficeMembershipPatchResource::setValidTo,
@ -102,10 +92,4 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeMembershipEntity::setMembershipFeeBillable) HsOfficeMembershipEntity::setMembershipFeeBillable)
); );
} }
private static HsOfficeDebitorEntity newDebitor(final UUID uuid) {
final var newDebitor = new HsOfficeDebitorEntity();
newDebitor.setUuid(uuid);
return newDebitor;
}
} }

Some files were not shown because too many files have changed in this diff Show More