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)
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.
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 {
private static Stringify<HsOfficeBankAccountEntity> toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount")
.withIdProp(HsOfficeBankAccountEntity::getIban)
.withProp(Fields.holder, HsOfficeBankAccountEntity::getHolder)
.withProp(Fields.iban, HsOfficeBankAccountEntity::getIban)
.withProp(Fields.bic, HsOfficeBankAccountEntity::getBic);
@Id
@ -59,8 +59,11 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
public static RbacView rbac() {
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
.withIdentityView(SQL.projection("iban || ':' || holder"))
.withIdentityView(SQL.projection("iban"))
.withUpdatableColumns("holder", "iban", "bic")
.toRole("global", GUEST).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
@ -75,6 +78,6 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
}
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) -> {
with.permission(SELECT);
});
})
.toRole(GLOBAL, GUEST).grantPermission(INSERT);
}
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 java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Optional;
import java.util.UUID;
import static java.util.Optional.ofNullable;
@ -28,13 +29,12 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class)
.withProp(HsOfficeCoopAssetsTransactionEntity::getMemberNumber)
.withIdProp(HsOfficeCoopAssetsTransactionEntity::getTaggedMemberNumber)
.withProp(HsOfficeCoopAssetsTransactionEntity::getValueDate)
.withProp(HsOfficeCoopAssetsTransactionEntity::getTransactionType)
.withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue)
.withProp(HsOfficeCoopAssetsTransactionEntity::getReference)
.withProp(HsOfficeCoopAssetsTransactionEntity::getComment)
.withSeparator(", ")
.quotedValues(false);
@Id
@ -76,8 +76,8 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUu
private String comment;
public Integer getMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::getMemberNumber).orElse(null);
public String getTaggedMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-?????");
}
@Override
@ -87,6 +87,6 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUu
@Override
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::getReference)
.withProp(HsOfficeCoopSharesTransactionEntity::getComment)
.withSeparator(", ")
.quotedValues(false);
@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.HsOfficeDebitorPatchResource;
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 org.apache.commons.lang3.Validate;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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 jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
@RestController
public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
@ -30,6 +37,9 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
@Autowired
private HsOfficeDebitorRepository debitorRepo;
@Autowired
private HsOfficeRelationRepository relRepo;
@PersistenceContext
private EntityManager em;
@ -53,22 +63,44 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
@Override
@Transactional
public ResponseEntity<HsOfficeDebitorResource> addDebitor(
final String currentUser,
final String assumedRoles,
final HsOfficeDebitorInsertResource body) {
String currentUser,
String assumedRoles,
HsOfficeDebitorInsertResource body) {
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 =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/hs/office/debitors/{id}")
.buildAndExpand(saved.getUuid())
.buildAndExpand(savedEntity.getUuid())
.toUri();
final var mapped = mapper.map(saved, HsOfficeDebitorResource.class);
final var mapped = mapper.map(savedEntity, HsOfficeDebitorResource.class);
return ResponseEntity.created(uri).body(mapped);
}
@ -119,6 +151,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
new HsOfficeDebitorEntityPatcher(em, current).apply(body);
final var saved = debitorRepo.save(current);
Hibernate.initialize(saved);
final var mapped = mapper.map(saved, HsOfficeDebitorResource.class);
return ResponseEntity.ok(mapped);
}

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.debitor;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
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.relation.HsOfficeRelationEntity;
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.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.*;
import java.io.IOException;
import java.util.Optional;
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.Nullable.NOT_NULL;
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.Role.*;
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.stringify.Stringify.stringify;
@ -30,7 +38,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Table(name = "hs_office_debitor_rv")
@Getter
@Setter
@Builder
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("Debitor")
@ -38,15 +46,11 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
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 =
stringify(HsOfficeDebitorEntity.class, "debitor")
.withProp(e -> DEBITOR_NUMBER_TAG + e.getDebitorNumber())
.withProp(HsOfficeDebitorEntity::getPartner)
.withIdProp(HsOfficeDebitorEntity::toShortString)
.withProp(e -> ofNullable(e.getDebitorRel()).map(HsOfficeRelationEntity::toShortString).orElse(null))
.withProp(HsOfficeDebitorEntity::getDefaultPrefix)
.withSeparator(": ")
.quotedValues(false);
@Id
@ -55,15 +59,28 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
private UUID uuid;
@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;
@Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)")
private Byte debitorNumberSuffix; // TODO maybe rather as a formatted String?
@ManyToOne
@JoinColumn(name = "billingcontactuuid")
private HsOfficeContactEntity billingContact; // TODO: migrate to billingPerson
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false)
@JoinColumn(name = "debitorreluuid", nullable = false)
private HsOfficeRelationEntity debitorRel;
@Column(name = "billable", nullable = 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 getDebitorNumberString() {
if (partner == null || partner.getPartnerNumber() == null || debitorNumberSuffix == null) {
return null;
}
return partner.getPartnerNumber() + String.format("%02d", debitorNumberSuffix);
return ofNullable(partner)
.filter(partner -> debitorNumberSuffix != null)
.map(HsOfficePartnerEntity::getPartnerNumber)
.map(Object::toString)
.map(partnerNumber -> partnerNumber + String.format("%02d", debitorNumberSuffix))
.orElse(null);
}
public Integer getDebitorNumber() {
return Optional.ofNullable(getDebitorNumberString()).map(Integer::parseInt).orElse(null);
return ofNullable(getDebitorNumberString()).map(Integer::parseInt).orElse(null);
}
@Override
@ -111,28 +130,28 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
public static RbacView rbac() {
return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
.withIdentityView(SQL.query("""
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
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
"""))
.withRestrictedViewOrderBy(SQL.projection("defaultPrefix"))
.withUpdatableColumns(
"debitorRel",
"debitorRelUuid",
"billable",
"debitorUuid",
"refundBankAccountUuid",
"vatId",
"vatCountryCode",
"vatBusiness",
"vatReverseCharge",
"defaultPrefix" /* TODO: do we want that updatable? */)
.createPermission(INSERT).grantedTo("global", ADMIN)
.toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class,
directlyFetchedByDependsOnColumn(),
@ -149,9 +168,16 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
.importEntityAlias("partnerRel", HsOfficeRelationEntity.class,
dependsOnColumn("partnerRelUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
dependsOnColumn("debitorRelUuid"),
fetchedBySql("""
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", AGENT).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)
@ -162,6 +188,6 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
}
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;
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.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
@ -23,9 +23,9 @@ class HsOfficeDebitorEntityPatcher implements EntityPatcher<HsOfficeDebitorPatch
@Override
public void apply(final HsOfficeDebitorPatchResource resource) {
OptionalFromJson.of(resource.getBillingContactUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "billingContact");
entity.setBillingContact(em.getReference(HsOfficeContactEntity.class, newValue));
OptionalFromJson.of(resource.getDebitorRelUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "debitorRel");
entity.setDebitorRel(em.getReference(HsOfficeRelationEntity.class, newValue));
});
Optional.ofNullable(resource.getBillable()).ifPresent(entity::setBillable);
OptionalFromJson.of(resource.getVatId()).ifPresent(entity::setVatId);

View File

@ -13,7 +13,10 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query("""
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
""")
List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int partnerNumber, byte debitorNumberSuffix);
@ -24,9 +27,15 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor
JOIN HsOfficePartnerEntity partner ON partner.uuid = debitor.partner.uuid
JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact.uuid
JOIN HsOfficePartnerEntity partner
ON partner.partnerRel.holder = debitor.debitorRel.anchor
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
OR partner.details.birthName 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.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
@ -32,9 +30,6 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Autowired
private HsOfficeMembershipRepository membershipRepo;
@PersistenceContext
private EntityManager em;
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeMembershipResource>> listMemberships(
@ -121,7 +116,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
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 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 lombok.*;
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.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.Stringifyable;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Type;
import jakarta.persistence.*;
import java.io.IOException;
import java.time.LocalDate;
import java.util.UUID;
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;
@Entity
@ -35,10 +45,8 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class)
.withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber())
.withProp(e -> e.getPartner().toShortString())
.withProp(e -> e.getMainDebitor().toShortString())
.withProp(e -> e.getValidity().asString())
.withProp(HsOfficeMembershipEntity::getReasonForTermination)
.withSeparator(", ")
.quotedValues(false);
@Id
@ -49,11 +57,6 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
@JoinColumn(name = "partneruuid")
private HsOfficePartnerEntity partner;
@ManyToOne
@Fetch(FetchMode.JOIN)
@JoinColumn(name = "maindebitoruuid")
private HsOfficeDebitorEntity mainDebitor;
@Column(name = "membernumbersuffix", length = 2)
private String memberNumberSuffix;
@ -114,4 +117,45 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
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;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import jakarta.persistence.EntityManager;
import java.util.Optional;
import java.util.UUID;
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
private final EntityManager em;
private final Mapper mapper;
private final HsOfficeMembershipEntity entity;
public HsOfficeMembershipEntityPatcher(
final EntityManager em,
final Mapper mapper,
final HsOfficeMembershipEntity entity) {
this.em = em;
this.mapper = mapper;
this.entity = entity;
}
@Override
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(
entity::setValidTo);
Optional.ofNullable(resource.getReasonForTermination())
@ -40,10 +29,4 @@ public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMe
OptionalFromJson.of(resource.getMembershipFeeBillable()).ifPresent(
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();
}
if (partnerRepo.deleteByUuid(partnerUuid) != 1 ||
// TODO: move to after delete trigger in partner
relationRepo.deleteByUuid(partnerToDelete.get().getPartnerRel().getUuid()) != 1 ) {
if (partnerRepo.deleteByUuid(partnerUuid) != 1) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
@ -142,8 +140,6 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final var entityToSave = new HsOfficePartnerEntity();
entityToSave.setPartnerNumber(body.getPartnerNumber());
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));
return entityToSave;
}

View File

@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -14,10 +13,8 @@ import java.io.IOException;
import java.time.LocalDate;
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.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.stringify.Stringify.stringify;
@ -40,7 +37,6 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
.withProp(HsOfficePartnerDetailsEntity::getBirthday)
.withProp(HsOfficePartnerDetailsEntity::getBirthName)
.withProp(HsOfficePartnerDetailsEntity::getDateOfDeath)
.withSeparator(", ")
.quotedValues(false);
@Id
@ -72,11 +68,12 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
public static RbacView rbac() {
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
.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
JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid
JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid
"""))
.withRestrictedViewOrderBy(SQL.expression("uuid"))
.withUpdatableColumns(
"registrationOffice",
"registrationNumber",
@ -84,17 +81,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
"birthName",
"birthday",
"dateOfDeath")
.createPermission(INSERT).grantedTo("global", ADMIN)
.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"))
.toRole("global", ADMIN).grantPermission(INSERT)
// The grants are defined in HsOfficePartnerEntity.rbac()
// 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 {
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;
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.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
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.rbac.rbacdef.RbacView;
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.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.util.Optional;
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.Permission.*;
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.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity
@ -36,10 +47,18 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@DisplayName("Partner")
public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
public static final String PARTNER_NUMBER_TAG = "P-";
private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner")
.withProp(HsOfficePartnerEntity::getPerson)
.withProp(HsOfficePartnerEntity::getContact)
.withSeparator(": ")
.withIdProp(HsOfficePartnerEntity::toShortString)
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelationEntity::getHolder)
.map(HsOfficePersonEntity::toShortString)
.orElse(null))
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelationEntity::getContact)
.map(HsOfficeContactEntity::toShortString)
.orElse(null))
.quotedValues(false);
@Id
@ -49,25 +68,19 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
private Integer partnerNumber;
@ManyToOne
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false)
@JoinColumn(name = "partnerreluuid", nullable = false)
private HsOfficeRelationEntity partnerRel;
// TODO: remove, is replaced by partnerRel
@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)
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true)
@JoinColumn(name = "detailsuuid")
@NotFound(action = NotFoundAction.IGNORE)
private HsOfficePartnerDetailsEntity details;
public String getTaggedPartnerNumber() {
return PARTNER_NUMBER_TAG + partnerNumber;
}
@Override
public String toString() {
return stringify.apply(this);
@ -75,22 +88,14 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
@Override
public String toShortString() {
return Optional.ofNullable(person).map(HsOfficePersonEntity::toShortString).orElse("<person=null>");
return getTaggedPartnerNumber();
}
public static RbacView rbac() {
return rbacViewFor("partner", HsOfficePartnerEntity.class)
.withIdentityView(SQL.query("""
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
"""))
.withUpdatableColumns(
"partnerRelUuid",
"personUuid",
"contactUuid")
.createPermission(INSERT).grantedTo("global", ADMIN)
.withIdentityView(SQL.projection("'P-' || partnerNumber"))
.withUpdatableColumns("partnerRelUuid")
.toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
directlyFetchedByDependsOnColumn(),
@ -108,6 +113,6 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
}
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;
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.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import jakarta.persistence.EntityManager;
import java.util.UUID;
class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> {
private final EntityManager em;
@ -21,19 +19,15 @@ class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatch
@Override
public void apply(final HsOfficePartnerPatchResource resource) {
OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "contact");
entity.setContact(em.getReference(HsOfficeContactEntity.class, newValue));
});
OptionalFromJson.of(resource.getPersonUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "person");
entity.setPerson(em.getReference(HsOfficePersonEntity.class, newValue));
OptionalFromJson.of(resource.getPartnerRelUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "partnerRel");
entity.setPartnerRel(em.getReference(HsOfficeRelationEntity.class, newValue));
});
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) {
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);
List<HsOfficePartnerEntity> findAll(); // TODO: move to a repo in test sources
@Query("""
SELECT partner FROM HsOfficePartnerEntity partner
JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact.uuid
JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
JOIN HsOfficeRelationEntity rel ON rel.uuid = partner.partnerRel.uuid
JOIN HsOfficeContactEntity contact ON contact.uuid = rel.contact.uuid
JOIN HsOfficePersonEntity person ON person.uuid = rel.holder.uuid
WHERE :name is null
OR partner.details.birthName 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)
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
.toRole("global", GUEST).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.permission(DELETE);
with.owningUser(CREATOR);
@ -84,6 +86,6 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
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.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.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -92,22 +92,26 @@ public class HsOfficeRelationEntity implements HasUuid, Stringifyable {
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
dependsOnColumn("anchorUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
NOT_NULL)
.importEntityAlias("holderPerson", HsOfficePersonEntity.class,
dependsOnColumn("holderUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
NOT_NULL)
.importEntityAlias("contact", HsOfficeContactEntity.class,
dependsOnColumn("contactUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
NOT_NULL)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
// TODO: if type=REPRESENTATIIVE
// with.incomingSuperRole("holderPerson", ADMIN);
with.permission(DELETE);
})
.createSubRole(ADMIN, (with) -> {
with.incomingSuperRole("anchorPerson", ADMIN);
// TODO: if type=REPRESENTATIIVE
// with.outgoingSuperRole("anchorPerson", OWNER);
with.permission(UPDATE);
})
.createSubRole(AGENT, (with) -> {
@ -120,10 +124,12 @@ public class HsOfficeRelationEntity implements HasUuid, Stringifyable {
with.outgoingSubRole("holderPerson", REFERRER);
with.outgoingSubRole("contact", REFERRER);
with.permission(SELECT);
});
})
.toRole("anchorPerson", ADMIN).grantPermission(INSERT);
}
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()) {
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) -> {

View File

@ -21,6 +21,7 @@ import java.util.UUID;
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.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.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -43,7 +44,6 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
.withProp(HsOfficeSepaMandateEntity::getReference)
.withProp(HsOfficeSepaMandateEntity::getAgreement)
.withProp(e -> e.getValidity().asString())
.withSeparator(", ")
.quotedValues(false);
@Id
@ -96,11 +96,27 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
public static RbacView rbac() {
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")
.importEntityAlias("debitorRel", HsOfficeRelationEntity.class, dependsOnColumn("debitorRelUuid"))
.importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, dependsOnColumn("bankAccountUuid"))
.importEntityAlias("debitorRel", HsOfficeRelationEntity.class,
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) -> {
with.owningUser(CREATOR);
@ -119,10 +135,12 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
with.incomingSuperRole("debitorRel", AGENT);
with.outgoingSubRole("debitorRel", TENANT);
with.permission(SELECT);
});
})
.toRole("debitorRel", ADMIN).grantPermission(INSERT);
}
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 $$
declare
row ${rawSuperTableName};
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO ${rawSubTableName} permissions for the related ${rawSuperTableName} rows');
FOR row IN SELECT * FROM ${rawSuperTableName}
LOOP
roleUuid := findRoleId(${rawSuperRoleDescriptor});
permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}');
call grantPermissionToRole(permissionUuid, roleUuid);
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', '${rawSubTableName}'),
${rawSuperRoleDescriptor});
END LOOP;
END;
$$;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ get:
in: query
required: false
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.
responses:
"200":
@ -28,7 +28,7 @@ get:
schema:
type: array
items:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation'
$ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
"401":
$ref: './error-responses.yaml#/components/responses/Unauthorized'
"403":
@ -46,7 +46,7 @@ post:
content:
'application/json':
schema:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationInsert'
$ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationInsert'
required: true
responses:
"201":
@ -54,7 +54,7 @@ post:
content:
'application/json':
schema:
$ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation'
$ref: './hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
"401":
$ref: './error-responses.yaml#/components/responses/Unauthorized'
"403":

View File

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

View File

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

View File

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

View File

@ -27,9 +27,9 @@ create table tx_context
txId bigint not null,
txTimestamp timestamp not null,
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,
currentRequest text not null
currentRequest text not null
);
create index on tx_context using brin (txTimestamp);

View File

@ -63,6 +63,7 @@ create or replace view rbacgrants_ev as
x.grantedByRoleUuid,
x.ascendantUuid as ascendantUuid,
x.descendantUuid as descendantUuid,
x.op as permOp, x.optablename as permOpTableName,
x.assumed
from (
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
) as ascendingIdName,
aro.objectTable, aro.uuid,
coalesce(
'role ' || dro.objectTable || '#' || findIdNameByObjectUuid(dro.objectTable, dro.uuid) || '.' || dr.roletype,
'perm ' || dp.op || ' on ' || dpo.objecttable || '#' || findIdNameByObjectUuid(dpo.objectTable, dpo.uuid)
( case
when dro is not null
then ('role ' || dro.objectTable || '#' || findIdNameByObjectUuid(dro.objectTable, dro.uuid) || '.' || dr.roletype)
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,
dro.objectTable, dro.uuid
from rbacgrants as g
dro.objectTable, dro.uuid,
dp.op, dp.optablename
from rbacgrants as g
left outer join rbacrole as ar on ar.uuid = g.ascendantUuid
left outer join rbacobject as aro on aro.uuid = ar.objectuuid

View File

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

View File

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

View File

@ -150,16 +150,14 @@ execute procedure updateTriggerForTestDomain_tf();
do language plpgsql $$
declare
row test_package;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO test_domain permissions for the related test_package rows');
FOR row IN SELECT * FROM test_package
LOOP
roleUuid := findRoleId(testPackageAdmin(row));
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_domain');
call grantPermissionToRole(permissionUuid, roleUuid);
call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'test_domain'),
testPackageAdmin(row));
END LOOP;
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:UPDATE{{contact:UPDATE}}
perm:contact:SELECT{{contact:SELECT}}
perm:contact:INSERT{{contact:INSERT}}
end
end
@ -39,5 +40,6 @@ role:contact:admin ==> role:contact:referrer
role:contact:owner ==> perm:contact:DELETE
role:contact:admin ==> perm:contact:UPDATE
role:contact:referrer ==> perm:contact:SELECT
role:global:guest ==> perm:contact:INSERT
```

View File

@ -1,4 +1,6 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
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)]
);
call buildRbacSystemForHsOfficeContact(NEW);
return NEW;
end; $$;
/*
An AFTER INSERT TRIGGER which creates the role structure for a new customer.
*/
create trigger createRbacRolesForHsOfficeContact_Trigger
after insert
on hs_office_contact
create trigger insertTriggerForHsOfficeContact_tg
after insert on hs_office_contact
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:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_contact', $idName$
target.label
call generateRbacIdentityViewFromProjection('hs_office_contact',
$idName$
label
$idName$);
--//
-- ============================================================================
--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$
label = new.label,
postalAddress = new.postalAddress,
emailAddresses = new.emailAddresses,
phoneNumbers = new.phoneNumbers
$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),
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[ ]
style person:permissions fill:#dd4901,stroke:white
perm:person:INSERT{{person:INSERT}}
perm:person:DELETE{{person:DELETE}}
perm:person:UPDATE{{person:UPDATE}}
perm:person:SELECT{{person:SELECT}}
@ -36,6 +37,7 @@ role:person:owner ==> role:person:admin
role:person:admin ==> role:person:referrer
%% granting permissions to roles
role:global:guest ==> perm:person:INSERT
role:person:owner ==> perm:person:DELETE
role:person:admin ==> perm:person:UPDATE
role:person:referrer ==> perm:person:SELECT

View File

@ -1,4 +1,6 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
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)]
);
call buildRbacSystemForHsOfficePerson(NEW);
return NEW;
end; $$;
/*
An AFTER INSERT TRIGGER which creates the role structure for a new customer.
*/
create trigger createRbacRolesForHsOfficePerson_Trigger
after insert
on hs_office_person
create trigger insertTriggerForHsOfficePerson_tg
after insert on hs_office_person
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:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_person', $idName$
concat(target.tradeName, target.familyName, target.givenName)
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', 'concat(target.tradeName, target.familyName, target.givenName)',
call generateRbacRestrictedView('hs_office_person',
$orderBy$
concat(tradeName, familyName, givenName)
$orderBy$,
$updates$
personType = new.personType,
tradeName = new.tradeName,
@ -91,49 +144,3 @@ call generateRbacRestrictedView('hs_office_person', 'concat(target.tradeName, ta
$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);
execute format('set local hsadminng.currentTask to %L', currentTask);
raise notice 'creating test person: %', fullName;
raise notice 'creating test person: % by %', fullName, emailAddr;
insert
into hs_office_person (persontype, tradename, givenname, familyname)
values (newPersonType, newTradeName, newGivenName, newFamilyName);
@ -67,9 +67,10 @@ do language plpgsql $$
call createHsOfficePersonTestData('NP', null, 'Fouler', 'Ellie');
call createHsOfficePersonTestData('LP', 'Second e.K.', 'Smith', 'Peter');
call createHsOfficePersonTestData('IF', 'Third OHG');
call createHsOfficePersonTestData('IF', 'Fourth eG');
call createHsOfficePersonTestData('LP', 'Fourth eG');
call createHsOfficePersonTestData('UF', 'Erben Bessler', 'Mel', 'Bessler');
call createHsOfficePersonTestData('NP', null, 'Bessler', 'Anita');
call createHsOfficePersonTestData('NP', null, 'Bessler', 'Bert');
call createHsOfficePersonTestData('NP', null, 'Winkler', 'Paul');
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
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph global
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeContact
subgraph holderPerson["`**holderPerson**`"]
direction TB
style hsOfficeContact fill:#eee
role:hsOfficeContact.admin[contact.admin]
--> role:hsOfficeContact.tenant[contact.tenant]
--> role:hsOfficeContact.guest[contact.guest]
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 hsOfficePerson
subgraph anchorPerson["`**anchorPerson**`"]
direction TB
style hsOfficePerson fill:#eee
role:hsOfficePerson.admin[person.admin]
--> role:hsOfficePerson.tenant[person.tenant]
--> role:hsOfficePerson.guest[person.guest]
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 hsOfficeRelation
subgraph contact["`**contact**`"]
direction TB
style contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficePerson#anchor.admin[person#anchor.admin]
--- role:hsOfficePerson.admin
role:hsOfficeRelation.owner[relation.owner]
%% permissions
role:hsOfficeRelation.owner --> perm:hsOfficeRelation.*{{relation.*}}
%% incoming
role:global.admin ---> role:hsOfficeRelation.owner
role:hsOfficePersonAdmin#anchor.admin
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}}
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
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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()
returns trigger
language plpgsql
strict as $$
create or replace procedure buildRbacSystemForHsOfficeRelation(
NEW hs_office_relation
)
language plpgsql as $$
declare
hsOfficeRelationTenant RbacRoleDescriptor;
newAnchor hs_office_person;
newHolder hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
newHolderPerson hs_office_person;
newAnchorPerson hs_office_person;
newContact hs_office_contact;
begin
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 as p where p.uuid = NEW.holderUuid into newHolder;
select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact;
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);
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(
hsOfficeRelationAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeRelationOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeRelationOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
-- the tenant role for those related users who can view the data
perform createRoleWithGrants(
hsOfficeRelationTenant,
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeRelationAdmin(NEW),
hsOfficePersonAdmin(newAnchor),
hsOfficePersonAdmin(newHolder),
hsOfficeContactAdmin(newContact)],
outgoingSubRoles => array[
hsOfficePersonTenant(newAnchor),
hsOfficePersonTenant(newHolder),
hsOfficeContactTenant(newContact)]
);
perform createRoleWithGrants(
hsOfficeRelationAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[
hsOfficePersonAdmin(newAnchorPerson),
hsOfficeRelationOwner(NEW)]
);
-- anchor and holder admin roles need each others tenant role
-- to be able to see the joined relation
-- TODO: this can probably be avoided through agent+guest roles
call grantRoleToRole(hsOfficePersonTenant(newAnchor), hsOfficePersonAdmin(newHolder));
call grantRoleToRole(hsOfficePersonTenant(newHolder), hsOfficePersonAdmin(newAnchor));
call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newHolder), hsOfficeContactAdmin(newContact));
perform createRoleWithGrants(
hsOfficeRelationAgent(NEW),
incomingSuperRoles => array[
hsOfficePersonAdmin(newHolderPerson),
hsOfficeRelationAdmin(NEW)]
);
elsif TG_OP = 'UPDATE' then
if OLD.contactUuid <> NEW.contactUuid then
-- nothing but the contact can be updated,
-- in other cases, a new relation needs to be created and the old updated
select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact;
call revokeRoleFromRole( hsOfficeRelationTenant, hsOfficeContactAdmin(oldContact) );
call grantRoleToRole( hsOfficeRelationTenant, hsOfficeContactAdmin(newContact) );
call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationTenant );
call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationTenant );
end if;
else
raise exception 'invalid usage of TRIGGER';
end if;
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);
return NEW;
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();
/*
An AFTER UPDATE TRIGGER which updates the role structure of a customer.
*/
create trigger updateRbacRolesForHsOfficeRelation_Trigger
after update
on hs_office_relation
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 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 || '-' ||
(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)
$idName$);
/*
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 $$
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:--//
-- ----------------------------------------------------------------------------
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$
contactUuid = new.contactUuid
$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(
holderPersonName varchar,
relationType HsOfficeRelationType,
anchorPersonTradeName varchar,
anchorPersonName varchar,
contactLabel varchar,
mark varchar default null)
language plpgsql as $$
@ -23,24 +23,28 @@ declare
contact hs_office_contact;
begin
idName := cleanIdentifier( anchorPersonTradeName || '-' || holderPersonName);
idName := cleanIdentifier( anchorPersonName || '-' || holderPersonName);
currentTask := 'creating relation test-data ' || idName;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
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
raise exception 'anchorPerson "%" not found', anchorPersonTradeName;
raise exception 'anchorPerson "%" not found', anchorPersonName;
end if;
select p.* from hs_office_person p
where p.tradeName = holderPersonName or p.familyName = holderPersonName
into holderPerson;
select p.*
into holderPerson
from hs_office_person p
where p.tradeName = holderPersonName or p.familyName = holderPersonName;
if holderPerson is null then
raise exception 'holderPerson "%" not found', holderPersonName;
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
raise exception 'contact "%" not found', contactLabel;
end if;
@ -87,17 +91,22 @@ do language plpgsql $$
begin
call createHsOfficeRelationTestData('First GmbH', 'PARTNER', 'Hostsharing eG', '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('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('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('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', 'DEBITOR', 'Smith', 'third contact');
call createHsOfficeRelationTestData('Smith', 'SUBSCRIBER', 'Third OHG', 'third contact', 'members-announce');
end;
$$;

View File

@ -33,23 +33,20 @@ create table hs_office_partner
(
uuid uuid unique references RbacObject (uuid) initially deferred,
partnerNumber numeric(5) unique not null,
partnerRelUuid uuid not null references hs_office_relation(uuid), -- TODO: delete 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
partnerRelUuid uuid not null references hs_office_relation(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.
*/
create or replace function deleteHsOfficeDetailsOnPartnerDelete()
create or replace function deleteHsOfficeDependentsOnPartnerDelete()
returns trigger
language PLPGSQL
as $$
@ -61,17 +58,24 @@ begin
if counter = 0 then
raise exception 'partner details % could not be deleted', OLD.detailsUuid;
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;
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
on hs_office_partner
for each row
execute procedure deleteHsOfficeDetailsOnPartnerDelete();
execute procedure deleteHsOfficeDependentsOnPartnerDelete();
-- ============================================================================
--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
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph global
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeContact
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style hsOfficeContact fill:#eee
role:hsOfficeContact.admin[contact.admin]
--> role:hsOfficeContact.tenant[contact.tenant]
--> role:hsOfficeContact.guest[contact.guest]
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 hsOfficePerson
subgraph partner["`**partner**`"]
direction TB
style hsOfficePerson fill:#eee
role:hsOfficePerson.admin[person.admin]
--> role:hsOfficePerson.tenant[person.tenant]
--> role:hsOfficePerson.guest[person.guest]
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 hsOfficePartnerDetails
subgraph partnerDetails["`**partnerDetails**`"]
direction TB
perm:hsOfficePartnerDetails.*{{partner.*}}
perm:hsOfficePartnerDetails.edit{{partner.edit}}
perm:hsOfficePartnerDetails.view{{partner.view}}
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 hsOfficePartner
role:hsOfficePartner.owner[partner.owner]
%% 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
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficePartner.guest[partner.guest]
%% permissions
role:hsOfficePartner.guest --> perm:hsOfficePartner.view{{partner.view}}
%% incoming
role:hsOfficePartner.tenant --> role:hsOfficePartner.guest
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,4 +1,6 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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()
returns trigger
language plpgsql
strict as $$
create or replace procedure buildRbacSystemForHsOfficePartner(
NEW hs_office_partner
)
language plpgsql as $$
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
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_relation as r where r.uuid = NEW.partnerReluuid into newPartnerRel;
select * from hs_office_person as p where p.uuid = NEW.personUuid into newPerson;
select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact;
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);
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: ===
perform createRoleWithGrants(
hsOfficePartnerOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()]
);
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 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);
return NEW;
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();
/*
An AFTER UPDATE TRIGGER which updates the role structure of a customer.
*/
create trigger updateRbacRolesForHsOfficePartner_Trigger
after update
on hs_office_partner
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 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)
|| '-' ||
(select idName from hs_office_contact_iv c where c.uuid = target.contactuuid)
$idName$);
/*
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;
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:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_partner',
'(select idName from hs_office_person_iv p where p.uuid = target.personUuid)',
$orderBy$
'P-' || partnerNumber
$orderBy$,
$updates$
partnerRelUuid = new.partnerRelUuid,
personUuid = new.personUuid,
contactUuid = new.contactUuid
partnerRelUuid = new.partnerRelUuid
$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
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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$
(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$);
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
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:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_partner_details',
'target.uuid', -- no specific order required
$orderBy$
uuid
$orderBy$,
$updates$
registrationOffice = new.registrationOffice,
registrationNumber = new.registrationNumber,
birthPlace = new.birthPlace,
birthName = new.birthName,
birthday = new.birthday,
dateOfDeath = new.dateOfDeath
birthPlace = new.birthPlace,
birthName = new.birthName,
birthday = new.birthday,
dateOfDeath = new.dateOfDeath
$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.
*/
create or replace procedure createHsOfficePartnerTestData(
mandantTradeName varchar,
partnerNumber numeric(5),
mandantTradeName varchar,
newPartnerNumber numeric(5),
partnerPersonName varchar,
contactLabel varchar )
contactLabel varchar )
language plpgsql as $$
declare
currentTask varchar;
idName varchar;
mandantPerson hs_office_person;
partnerRel hs_office_relation;
partnerRel hs_office_relation;
relatedPerson hs_office_person;
relatedContact hs_office_contact;
relatedDetailsUuid uuid;
begin
idName := cleanIdentifier( partnerPersonName|| '-' || contactLabel);
@ -38,9 +37,6 @@ begin
select p.* from hs_office_person p
where p.tradeName = partnerPersonName or p.familyName = partnerPersonName
into relatedPerson;
select c.* from hs_office_contact c
where c.label = contactLabel
into relatedContact;
select r.* from hs_office_relation r
where r.type = 'PARTNER'
@ -53,7 +49,6 @@ begin
raise notice 'creating test partner: %', idName;
raise notice '- using partnerRel (%): %', partnerRel.uuid, partnerRel;
raise notice '- using person (%): %', relatedPerson.uuid, relatedPerson;
raise notice '- using contact (%): %', relatedContact.uuid, relatedContact;
if relatedPerson.persontype = 'NP' then
insert
@ -68,8 +63,8 @@ begin
end if;
insert
into hs_office_partner (uuid, partnerNumber, partnerRelUuid, personuuid, contactuuid, detailsUuid)
values (uuid_generate_v4(), partnerNumber, partnerRel.uuid, relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid);
into hs_office_partner (uuid, partnerNumber, partnerRelUuid, detailsUuid)
values (uuid_generate_v4(), newPartnerNumber, partnerRel.uuid, relatedDetailsUuid);
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
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph global
style global fill: lightgray
role:global.admin[global.admin]
end
subgraph hsOfficeBankAccount
subgraph bankAccount["`**bankAccount**`"]
direction TB
style hsOfficeBankAccount fill: lightgreen
user:hsOfficeBankAccount.creator([bankAccount.creator])
style bankAccount fill:#dd4901,stroke:#274d6e,stroke-width:8px
role:hsOfficeBankAccount.owner[[bankAccount.owner]]
%% permissions
role:hsOfficeBankAccount.owner --> perm:hsOfficeBankAccount.*{{hsOfficeBankAccount.delete}}
%% incoming
role:global.admin --> role:hsOfficeBankAccount.owner
user:hsOfficeBankAccount.creator ---> role:hsOfficeBankAccount.owner
role:hsOfficeBankAccount.admin[[bankAccount.admin]]
%% 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{{hsOfficeBankAccount.view}}
%% incoming
role:hsOfficeBankAccount.tenant ---> role:hsOfficeBankAccount.guest
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:INSERT{{bankAccount:INSERT}}
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: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
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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
language plpgsql
strict as $$
begin
if TG_OP <> 'INSERT' then
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)]
);
call buildRbacSystemForHsOfficeBankAccount(NEW);
return NEW;
end; $$;
/*
An AFTER INSERT TRIGGER which creates the role structure for a new customer.
*/
create trigger createRbacRolesForHsOfficeBankAccount_Trigger
after insert
on hs_office_bankaccount
create trigger insertTriggerForHsOfficeBankAccount_tg
after insert on hs_office_bankaccount
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:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_bankaccount', $idName$
target.holder
call generateRbacIdentityViewFromProjection('hs_office_bankaccount',
$idName$
iban
$idName$);
--//
-- ============================================================================
--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$
holder = new.holder,
iban = new.iban,
bic = new.bic
$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
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph global
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeBankAccount
subgraph bankAccount["`**bankAccount**`"]
direction TB
style hsOfficeBankAccount fill:#eee
role:hsOfficeBankAccount.owner[bankAccount.owner]
--> role:hsOfficeBankAccount.admin[bankAccount.admin]
--> role:hsOfficeBankAccount.tenant[bankAccount.tenant]
--> role:hsOfficeBankAccount.guest[bankAccount.guest]
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 hsOfficeDebitor
subgraph debitorRel.contact["`**debitorRel.contact**`"]
direction TB
style hsOfficeDebitor fill:#eee
role:hsOfficeDebitor.owner[debitor.admin]
--> role:hsOfficeDebitor.admin[debitor.admin]
--> role:hsOfficeDebitor.agent[debitor.agent]
--> role:hsOfficeDebitor.tenant[debitor.tenant]
--> role:hsOfficeDebitor.guest[debitor.guest]
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 hsOfficeSepaMandate
role:hsOfficeSepaMandate.owner[sepaMandate.owner]
%% 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
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
role:hsOfficeSepaMandate.guest[sepaMandate.guest]
%% permissions
role:hsOfficeSepaMandate.guest --> perm:hsOfficeSepaMandate.view{{sepaMandate.view}}
%% incoming
role:hsOfficeSepaMandate.tenant --> role:hsOfficeSepaMandate.guest
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}}
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
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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()
returns trigger
language plpgsql
strict as $$
create or replace procedure buildRbacSystemForHsOfficeSepaMandate(
NEW hs_office_sepamandate
)
language plpgsql as $$
declare
newHsOfficeDebitor hs_office_debitor;
newHsOfficeBankAccount hs_office_bankAccount;
newBankAccount hs_office_bankaccount;
newDebitorRel hs_office_relation;
begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_debitor as p where p.uuid = NEW.debitorUuid into newHsOfficeDebitor;
select * from hs_office_bankAccount as c where c.uuid = NEW.bankAccountUuid into newHsOfficeBankAccount;
SELECT * FROM hs_office_bankaccount WHERE uuid = NEW.bankAccountUuid INTO newBankAccount;
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(
hsOfficeSepaMandateOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()]
);
perform createRoleWithGrants(
hsOfficeSepaMandateOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
hsOfficeSepaMandateAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeSepaMandateOwner(NEW)],
outgoingSubRoles => array[hsOfficeBankAccountTenant(newHsOfficeBankAccount)]
);
perform createRoleWithGrants(
hsOfficeSepaMandateAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeSepaMandateOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeSepaMandateAgent(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW), hsOfficeDebitorAdmin(newHsOfficeDebitor), hsOfficeBankAccountAdmin(newHsOfficeBankAccount)],
outgoingSubRoles => array[hsOfficeDebitorTenant(newHsOfficeDebitor)]
);
perform createRoleWithGrants(
hsOfficeSepaMandateAgent(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)],
outgoingSubRoles => array[
hsOfficeBankAccountReferrer(newBankAccount),
hsOfficeRelationAgent(newDebitorRel)]
);
perform createRoleWithGrants(
hsOfficeSepaMandateTenant(NEW),
incomingSuperRoles => array[hsOfficeSepaMandateAgent(NEW)],
outgoingSubRoles => array[hsOfficeDebitorGuest(newHsOfficeDebitor), hsOfficeBankAccountGuest(newHsOfficeBankAccount)]
);
perform createRoleWithGrants(
hsOfficeSepaMandateGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeSepaMandateTenant(NEW)]
);
-- === END of code generated from Mermaid flowchart. ===
else
raise exception 'invalid usage of TRIGGER';
end if;
perform createRoleWithGrants(
hsOfficeSepaMandateReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeBankAccountAdmin(newBankAccount),
hsOfficeRelationAgent(newDebitorRel),
hsOfficeSepaMandateAgent(NEW)],
outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
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
on hs_office_sepamandate
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 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:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_sepamandate',
orderby => 'target.reference',
columnUpdates => $updates$
$orderBy$
validity
$orderBy$,
$updates$
reference = new.reference,
agreement = new.agreement,
validity = new.validity
$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.
*/
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 $$
declare
currentTask varchar;
idName varchar;
relatedDebitor hs_office_debitor;
relatedBankAccount hs_office_bankAccount;
begin
idName := cleanIdentifier( tradeNameAndHolderName);
currentTask := 'creating SEPA-mandate test-data ' || idName;
currentTask := 'creating SEPA-mandate test-data ' || forPartnerNumber::text || forDebitorSuffix::text;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask);
select debitor.* from hs_office_debitor debitor
join hs_office_partner parter on parter.uuid = debitor.partnerUuid
join hs_office_person person on person.uuid = parter.personUuid
where person.tradeName = tradeNameAndHolderName into relatedDebitor;
select c.* from hs_office_bankAccount c where c.holder = tradeNameAndHolderName into relatedBankAccount;
select debitor.* into relatedDebitor
from hs_office_debitor debitor
join hs_office_relation debitorRel on debitorRel.uuid = debitor.debitorRelUuid
join hs_office_relation partnerRel on partnerRel.holderUuid = debitorRel.anchorUuid
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 bankAccount (%): %', relatedBankAccount.uuid, relatedBankAccount;
insert
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; $$;
--//
@ -43,9 +48,9 @@ end; $$;
do language plpgsql $$
begin
call createHsOfficeSepaMandateTestData('First GmbH');
call createHsOfficeSepaMandateTestData('Second e.K.');
call createHsOfficeSepaMandateTestData('Third OHG');
call createHsOfficeSepaMandateTestData(10001, 11, 'DE02120300000000202051', 'ref-10001-11');
call createHsOfficeSepaMandateTestData(10002, 12, 'DE02100500000054540402', 'ref-10002-12');
call createHsOfficeSepaMandateTestData(10003, 13, 'DE02300209000106531065', 'ref-10003-13');
end;
$$;
--//

View File

@ -7,10 +7,9 @@
create table hs_office_debitor
(
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,
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?
vatCountryCode varchar(2),
vatBusiness boolean not null,
@ -20,11 +19,43 @@ create table hs_office_debitor
constraint check_default_prefix check (
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:--//
-- ----------------------------------------------------------------------------

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
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph global
style global fill:#eee
role:global.admin[global.admin]
end
subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"]
direction TB
style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph office
style office fill:#eee
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 debitorRel.anchorPerson:roles[ ]
style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white
subgraph hsOfficeSepaMandate
role:hsOfficeSepaMandate.owner[sepaMandate.owner]
%% 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
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,4 +1,6 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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()
returns trigger
language plpgsql
strict as $$
create or replace procedure buildRbacSystemForHsOfficeDebitor(
NEW hs_office_debitor
)
language plpgsql as $$
declare
hsOfficeDebitorTenant RbacRoleDescriptor;
oldPartner hs_office_partner;
newPartner hs_office_partner;
newPerson hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
newBankAccount hs_office_bankaccount;
oldBankAccount hs_office_bankaccount;
newPartnerRel hs_office_relation;
newDebitorRel hs_office_relation;
newRefundBankAccount hs_office_bankaccount;
begin
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_person as p where p.uuid = newPartner.personUuid into newPerson;
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_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;
perform createRoleWithGrants(
hsOfficeDebitorOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()],
grantedByRole => globalAdmin()
);
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));
perform createRoleWithGrants(
hsOfficeDebitorAdmin(NEW),
permissions => array['UPDATE'],
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 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);
return NEW;
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();
/*
An AFTER UPDATE TRIGGER which updates the role structure of a debitor.
*/
create trigger updateRbacRolesForHsOfficeDebitor_Trigger
after update
on hs_office_debitor
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 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:--//
-- ----------------------------------------------------------------------------
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:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_debitor', 'target.debitorNumberSuffix',
call generateRbacRestrictedView('hs_office_debitor',
$orderBy$
defaultPrefix
$orderBy$,
$updates$
partnerUuid = new.partnerUuid, -- TODO: remove? should never do anything
debitorRelUuid = new.debitorRelUuid,
billable = new.billable,
billingContactUuid = new.billingContactUuid,
debitorNumberSuffix = new.debitorNumberSuffix, -- TODO: Should it be allowed to updated this value?
refundBankAccountUuid = new.refundBankAccountUuid,
vatId = new.vatId,
vatCountryCode = new.vatCountryCode,
vatBusiness = new.vatBusiness,
vatreversecharge = new.vatreversecharge,
defaultPrefix = new.defaultPrefix -- TODO: Should it be allowed to updated this value?
vatReverseCharge = new.vatReverseCharge,
defaultPrefix = new.defaultPrefix
$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.
*/
create or replace procedure createHsOfficeDebitorTestData(
debitorNumberSuffix numeric(5),
partnerTradeName varchar,
billingContactLabel varchar,
defaultPrefix varchar
withDebitorNumberSuffix numeric(5),
forPartnerPersonName varchar,
forBillingContactLabel varchar,
withDefaultPrefix varchar
)
language plpgsql as $$
declare
currentTask varchar;
idName varchar;
relatedPartner hs_office_partner;
relatedContact hs_office_contact;
relatedDebitorRelUuid uuid;
relatedBankAccountUuid uuid;
begin
idName := cleanIdentifier( partnerTradeName|| '-' || billingContactLabel);
idName := cleanIdentifier( forPartnerPersonName|| '-' || forBillingContactLabel);
currentTask := 'creating debitor test-data ' || idName;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask);
select partner.* from hs_office_partner partner
join hs_office_person person on person.uuid = partner.personUuid
where person.tradeName = partnerTradeName into relatedPartner;
select c.* from hs_office_contact c where c.label = billingContactLabel into relatedContact;
select b.uuid from hs_office_bankaccount b where b.holder = partnerTradeName into relatedBankAccountUuid;
select debitorRel.uuid
into relatedDebitorRelUuid
from hs_office_relation debitorRel
join hs_office_person person on person.uuid = debitorRel.holderUuid
and (person.tradeName = forPartnerPersonName or person.familyName = forPartnerPersonName)
where debitorRel.type = 'DEBITOR';
raise notice 'creating test debitor: % (#%)', idName, debitorNumberSuffix;
raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner;
raise notice '- using billingContact (%): %', relatedContact.uuid, relatedContact;
select b.uuid
into relatedBankAccountUuid
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
into hs_office_debitor (uuid, partneruuid, debitornumbersuffix, billable, billingcontactuuid, vatbusiness, vatreversecharge, refundbankaccountuuid, defaultprefix)
values (uuid_generate_v4(), relatedPartner.uuid, debitorNumberSuffix, true, relatedContact.uuid, true, false, relatedBankAccountUuid, defaultPrefix);
into hs_office_debitor (uuid, debitorRelUuid, debitornumbersuffix, billable, vatbusiness, vatreversecharge, refundbankaccountuuid, defaultprefix)
values (uuid_generate_v4(), relatedDebitorRelUuid, withDebitorNumberSuffix, true, true, false, relatedBankAccountUuid, withDefaultPrefix);
end; $$;
--//

View File

@ -12,7 +12,6 @@ create table if not exists hs_office_membership
(
uuid uuid unique references RbacObject (uuid) initially deferred,
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::text ~ '^[0-9][0-9]$'),
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
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph global
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeDebitor
subgraph partnerRel["`**partnerRel**`"]
direction TB
style hsOfficeDebitor fill:#eee
role:hsOfficeDebitor.owner[debitor.owner]
--> role:hsOfficeDebitor.admin[debitor.admin]
--> role:hsOfficeDebitor.tenant[debitor.tenant]
--> role:hsOfficeDebitor.guest[debitor.guest]
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
subgraph hsOfficePartner
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style hsOfficePartner fill:#eee
role:hsOfficePartner.owner[partner.admin]
--> role:hsOfficePartner.admin[partner.admin]
--> role:hsOfficePartner.agent[partner.agent]
--> role:hsOfficePartner.tenant[partner.tenant]
--> role:hsOfficePartner.guest[partner.guest]
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 hsOfficeMembership
role:hsOfficeMembership.owner[membership.owner]
%% 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
subgraph membership["`**membership**`"]
direction TB
style membership fill:#dd4901,stroke:#274d6e,stroke-width:8px
role:hsOfficeMembership.guest[membership.guest]
%% permissions
role:hsOfficeMembership.guest --> perm:hsOfficeMembership.view{{membership.view}}
%% incoming
role:hsOfficeMembership.tenant --> role:hsOfficeMembership.guest
role:hsOfficePartner.tenant --> role:hsOfficeMembership.guest
role:hsOfficeDebitor.tenant --> role:hsOfficeMembership.guest
subgraph membership:roles[ ]
style membership:roles fill:#dd4901,stroke:white
role:membership:owner[[membership:owner]]
role:membership:admin[[membership:admin]]
role:membership:referrer[[membership:referrer]]
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
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
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
-- ============================================================================
--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()
returns trigger
language plpgsql
strict as $$
create or replace procedure buildRbacSystemForHsOfficeMembership(
NEW hs_office_membership
)
language plpgsql as $$
declare
newHsOfficePartner hs_office_partner;
newHsOfficeDebitor hs_office_debitor;
newPartnerRel hs_office_relation;
begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_partner as p where p.uuid = NEW.partnerUuid into newHsOfficePartner;
select * from hs_office_debitor as c where c.uuid = NEW.mainDebitorUuid into newHsOfficeDebitor;
SELECT partnerRel.*
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(
hsOfficeMembershipOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()]
);
perform createRoleWithGrants(
hsOfficeMembershipAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[
hsOfficeMembershipOwner(NEW),
hsOfficeRelationAgent(newPartnerRel)]
);
perform createRoleWithGrants(
hsOfficeMembershipAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)]
);
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;
perform createRoleWithGrants(
hsOfficeMembershipReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeMembershipAdmin(NEW)],
outgoingSubRoles => array[hsOfficeRelationTenant(newPartnerRel)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
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
on hs_office_membership
create or replace function insertTriggerForHsOfficeMembership_tf()
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
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) ||
memberNumberSuffix ||
':' || (select split_part(idName, ':', 2) from hs_office_partner_iv p where p.uuid = target.partnerUuid)
$idName$);
/*
Creates INSERT INTO hs_office_membership permissions for the related global rows.
*/
do language plpgsql $$
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:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_membership',
orderby => 'target.memberNumberSuffix',
columnUpdates => $updates$
$orderBy$
validity
$orderBy$,
$updates$
validity = new.validity,
reasonForTermination = new.reasonForTermination,
membershipFeeBillable = new.membershipFeeBillable
membershipFeeBillable = new.membershipFeeBillable,
reasonForTermination = new.reasonForTermination
$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.
*/
create or replace procedure createHsOfficeMembershipTestData(
forPartnerTradeName varchar,
forMainDebitorNumberSuffix numeric,
forPartnerNumber numeric(5),
newMemberNumberSuffix char(2) )
language plpgsql as $$
declare
currentTask varchar;
idName varchar;
relatedPartner hs_office_partner;
relatedDebitor hs_office_debitor;
begin
idName := cleanIdentifier( forPartnerTradeName || '#' || forMainDebitorNumberSuffix);
currentTask := 'creating Membership test-data ' || idName;
currentTask := 'creating Membership test-data ' ||
'P-' || forPartnerNumber::text ||
'M-...' || newMemberNumberSuffix;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask);
select partner.* from hs_office_partner partner
join hs_office_person person on person.uuid = partner.personUuid
where person.tradeName = forPartnerTradeName into relatedPartner;
select d.* from hs_office_debitor d
where d.partneruuid = relatedPartner.uuid
and d.debitorNumberSuffix = forMainDebitorNumberSuffix
into relatedDebitor;
where partner.partnerNumber = forPartnerNumber into relatedPartner;
raise notice 'creating test Membership: %', idName;
raise notice 'creating test Membership: M-% %', forPartnerNumber, newMemberNumberSuffix;
raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner;
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
insert
into hs_office_membership (uuid, partneruuid, maindebitoruuid, memberNumberSuffix, validity, reasonfortermination)
values (uuid_generate_v4(), relatedPartner.uuid, relatedDebitor.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'NONE');
into hs_office_membership (uuid, partneruuid, memberNumberSuffix, validity, reasonfortermination)
values (uuid_generate_v4(), relatedPartner.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'NONE');
end; $$;
--//
@ -48,9 +40,9 @@ end; $$;
do language plpgsql $$
begin
call createHsOfficeMembershipTestData('First GmbH', 11, '01');
call createHsOfficeMembershipTestData('Second e.K.', 12, '02');
call createHsOfficeMembershipTestData('Third OHG', 13, '03');
call createHsOfficeMembershipTestData(10001, '01');
call createHsOfficeMembershipTestData(10002, '02');
call createHsOfficeMembershipTestData(10003, '03');
end;
$$;
--//

View File

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

View File

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

View File

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

View File

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

View File

@ -102,23 +102,21 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTestWithC
final var roles = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(roles)).containsExactlyInAnyOrder(Array.from(
initialRoleNames,
"hs_office_bankaccount#sometempaccC.owner",
"hs_office_bankaccount#sometempaccC.admin",
"hs_office_bankaccount#sometempaccC.tenant",
"hs_office_bankaccount#sometempaccC.guest"
"hs_office_bankaccount#DE25500105176934832579.owner",
"hs_office_bankaccount#DE25500105176934832579.admin",
"hs_office_bankaccount#DE25500105176934832579.referrer"
));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames,
"{ grant perm DELETE on hs_office_bankaccount#sometempaccC to role hs_office_bankaccount#sometempaccC.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#sometempaccC.owner to user selfregistered-user-drew@hostsharing.org by global#global.admin 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#DE25500105176934832579.owner to role global#global.admin by system 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#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 }",
"{ 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 }",
null
));
}
@ -241,10 +239,6 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTestWithC
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll());
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
final var result = jpaAttempt.transacted(() -> {

View File

@ -105,19 +105,18 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTestWithClean
initialRoleNames,
"hs_office_contact#anothernewcontact.owner",
"hs_office_contact#anothernewcontact.admin",
"hs_office_contact#anothernewcontact.tenant",
"hs_office_contact#anothernewcontact.guest"
"hs_office_contact#anothernewcontact.referrer"
));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.from(
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames,
"{ 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 role hs_office_contact#anothernewcontact.tenant to role hs_office_contact#anothernewcontact.admin 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 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 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.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 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 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.referrer by system 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
@Accepts({ "CoopAssetTransaction:X(Access Control)" })
void contactAdminUser_canGetRelatedCoopAssetTransaction() {
void partnerPersonUser_canGetRelatedCoopAssetTransaction() {
context.define("superuser-alex@hostsharing.net");
final var givenCoopAssetTransactionUuid = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(
null,
@ -285,7 +285,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased
RestAssured // @formatter:off
.given()
.header("current-user", "contact-admin@firstcontact.example.com")
.header("current-user", "person-FirstGmbH@example.com")
.port(port)
.when()
.get("http://localhost/api/hs/office/coopassetstransactions/" + givenCoopAssetTransactionUuid)

View File

@ -23,27 +23,27 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
void toStringContainsAlmostAllPropertiesAccount() {
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
void toShortStringContainsOnlyMemberNumberSuffixAndSharesCountOnly() {
final var result = givenCoopAssetTransaction.toShortString();
assertThat(result).isEqualTo("1000101+128.00");
assertThat(result).isEqualTo("M-1000101:+128.00");
}
@Test
void toStringWithEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopAssetsTransaction.toString();
assertThat(result).isEqualTo("CoopAssetsTransaction()");
assertThat(result).isEqualTo("CoopAssetsTransaction(M-?????: )");
}
@Test
void toShortStringEmptyTransactionDoesNotThrowException() {
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_", ""))
.containsExactlyInAnyOrder(Array.fromFormatted(
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));
}
@ -141,17 +141,17 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then
allTheseCoopAssetsTransactionsAreReturned(
result,
"CoopAssetsTransaction(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(1000101, 2022-10-20, ADJUSTMENT, 128.00, ref 1000101-3, some adjustment)",
"CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)",
"CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)",
"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(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: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)",
"CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)",
"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(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: 2010-03-15, DEPOSIT, 320.00, ref 1000303-1, initial deposit)",
"CoopAssetsTransaction(M-1000303: 2021-09-01, DISBURSAL, -128.00, ref 1000303-2, partial disbursal)",
"CoopAssetsTransaction(M-1000303: 2022-10-20, ADJUSTMENT, 128.00, ref 1000303-3, some adjustment)");
}
@Test
@ -169,9 +169,9 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then
allTheseCoopAssetsTransactionsAreReturned(
result,
"CoopAssetsTransaction(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(1000202, 2022-10-20, ADJUSTMENT, 128.00, ref 1000202-3, some adjustment)");
"CoopAssetsTransaction(M-1000202: 2010-03-15, DEPOSIT, 320.00, ref 1000202-1, initial deposit)",
"CoopAssetsTransaction(M-1000202: 2021-09-01, DISBURSAL, -128.00, ref 1000202-2, partial disbursal)",
"CoopAssetsTransaction(M-1000202: 2022-10-20, ADJUSTMENT, 128.00, ref 1000202-3, some adjustment)");
}
@Test
@ -189,13 +189,13 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then
allTheseCoopAssetsTransactionsAreReturned(
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
public void normalUser_canViewOnlyRelatedCoopAssetsTransactions() {
public void partnerPersonAdmin_canViewRelatedCoopAssetsTransactions() {
// given:
context("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.admin");
context("superuser-alex@hostsharing.net", "hs_office_person#FirstGmbH.admin");
// when:
final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(
@ -206,9 +206,9 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then:
exactlyTheseCoopAssetsTransactionsAreReturned(
result,
"CoopAssetsTransaction(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(1000101, 2022-10-20, ADJUSTMENT, 128.00, ref 1000101-3, some adjustment)");
"CoopAssetsTransaction(M-1000101: 2010-03-15, DEPOSIT, 320.00, ref 1000101-1, initial deposit)",
"CoopAssetsTransaction(M-1000101: 2021-09-01, DISBURSAL, -128.00, ref 1000101-2, partial disbursal)",
"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
@Accepts({"CoopShareTransaction:X(Access Control)"})
void contactAdminUser_canGetRelatedCoopShareTransaction() {
void partnerPersonUser_canGetRelatedCoopShareTransaction() {
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();
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("""
{
"transactionType": "SUBSCRIPTION",
"shareCount": 4
}
""")); // @formatter:on
.given()
.header("current-user", "person-FirstGmbH@example.com")
.port(port)
.when()
.get("http://localhost/api/hs/office/coopsharestransactions/" + givenCoopShareTransactionUuid)
.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");
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream()
.map(s -> s.replace("FirstGmbH-firstcontact", "..."))
.map(s -> s.replace("hs_office_", ""))
.toList();
@ -109,11 +108,10 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
final var all = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(initialRoleNames)); // no new roles created
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("FirstGmbH-firstcontact", "..."))
.map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(Array.fromFormatted(
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));
}
@ -194,7 +192,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
@Test
public void normalUser_canViewOnlyRelatedCoopSharesTransactions() {
// 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:
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.contact.HsOfficeContactRepository;
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.test.Accepts;
import net.hostsharing.test.JpaAttempt;
@ -24,6 +27,7 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
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.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat;
@ -57,6 +61,12 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
@Autowired
HsOfficeBankAccountRepository bankAccountRepo;
@Autowired
HsOfficePersonRepository personRepo;
@Autowired
HsOfficeRelationRepository relRepo;
@Autowired
JpaAttempt jpaAttempt;
@ -81,37 +91,135 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType("application/json")
.body("", lenientlyEquals("""
[
{
"debitorNumber": 1000111,
"debitorNumberSuffix": 11,
"partner": { "person": { "personType": "LEGAL_PERSON" } },
"billingContact": { "label": "first contact" },
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true,
"refundBankAccount": { "holder": "First GmbH" }
},
{
"debitorNumber": 1000212,
"debitorNumberSuffix": 12,
"partner": { "person": { "tradeName": "Second e.K." } },
"billingContact": { "label": "second contact" },
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true,
"refundBankAccount": { "holder": "Second e.K." }
},
{
"debitorNumber": 1000313,
"debitorNumberSuffix": 13,
"partner": { "person": { "tradeName": "Third OHG" } },
"billingContact": { "label": "third contact" },
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true,
"refundBankAccount": { "holder": "Third OHG" }
}
]
{
"debitorRel": {
"anchor": {
"personType": "LEGAL_PERSON",
"tradeName": "First GmbH",
"givenName": null,
"familyName": null
},
"holder": {
"personType": "LEGAL_PERSON",
"tradeName": "First GmbH",
"givenName": null,
"familyName": null
},
"type": "DEBITOR",
"mark": null,
"contact": {
"label": "first contact",
"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",
"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
}
@ -132,8 +240,10 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
[
{
"debitorNumber": 1000212,
"partner": { "person": { "tradeName": "Second e.K." } },
"billingContact": { "label": "second contact" },
"partner": { "partnerNumber": 10002 },
"debitorRel": {
"contact": { "label": "second contact" }
},
"vatId": null,
"vatCountryCode": null,
"vatBusiness": true
@ -154,6 +264,17 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Third").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("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
.given()
@ -161,8 +282,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType(ContentType.JSON)
.body("""
{
"partnerUuid": "%s",
"billingContactUuid": "%s",
"debitorRelUuid": "%s",
"debitorNumberSuffix": "%s",
"billable": "true",
"vatId": "VAT123456",
@ -172,7 +292,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"refundBankAccountUuid": "%s",
"defaultPrefix": "for"
}
""".formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorSuffix, givenBankAccount.getUuid()))
""".formatted( givenDebitorRelUUid, ++nextDebitorSuffix, givenBankAccount.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/office/debitors")
@ -182,8 +302,8 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.body("uuid", isUuidValid())
.body("vatId", is("VAT123456"))
.body("defaultPrefix", is("for"))
.body("billingContact.label", is(givenContact.getLabel()))
.body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName()))
.body("debitorRel.contact.label", is(givenContact.getLabel()))
.body("debitorRel.holder.tradeName", is(givenBillingPerson.getTradeName()))
.body("refundBankAccount.holder", is(givenBankAccount.getHolder()))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
@ -206,15 +326,23 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"partnerUuid": "%s",
"billingContactUuid": "%s",
"debitorNumberSuffix": "%s",
"defaultPrefix": "for",
"billable": "true",
"vatReverseCharge": "false"
}
""".formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorSuffix))
{
"debitorRel": {
"type": "DEBITOR",
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
},
"debitorNumberSuffix": "%s",
"defaultPrefix": "for",
"billable": "true",
"vatReverseCharge": "false"
}
""".formatted(
givenPartner.getPartnerRel().getHolder().getUuid(),
givenPartner.getPartnerRel().getHolder().getUuid(),
givenContact.getUuid(),
++nextDebitorSuffix))
.port(port)
.when()
.post("http://localhost/api/hs/office/debitors")
@ -222,8 +350,8 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.statusCode(201)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("billingContact.label", is(givenContact.getLabel()))
.body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName()))
.body("debitorRel.contact.label", is(givenContact.getLabel()))
.body("partner.partnerRel.holder.tradeName", is(givenPartner.getPartnerRel().getHolder().getTradeName()))
.body("vatId", equalTo(null))
.body("vatCountryCode", equalTo(null))
.body("vatBusiness", equalTo(false))
@ -250,19 +378,22 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"partnerUuid": "%s",
"billingContactUuid": "%s",
"debitorNumberSuffix": "%s",
"billable": "true",
"vatId": "VAT123456",
"vatCountryCode": "DE",
"vatBusiness": true,
"vatReverseCharge": "false",
"defaultPrefix": "thi"
}
"""
.formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorSuffix))
{
"debitorRel": {
"type": "DEBITOR",
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
},
"debitorNumberSuffix": "%s",
"defaultPrefix": "for",
"billable": "true",
"vatReverseCharge": "false"
}
""".formatted(
givenPartner.getPartnerRel().getAnchor().getUuid(),
givenPartner.getPartnerRel().getAnchor().getUuid(),
givenContactUuid, ++nextDebitorSuffix))
.port(port)
.when()
.post("http://localhost/api/hs/office/debitors")
@ -273,10 +404,10 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
}
@Test
void globalAdmin_canNotAddDebitor_ifPartnerDoesNotExist() {
void globalAdmin_canNotAddDebitor_ifDebitorRelDoesNotExist() {
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 location = RestAssured // @formatter:off
@ -284,24 +415,20 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"partnerUuid": "%s",
"billingContactUuid": "%s",
"debitorNumberSuffix": "%s",
"billable": "true",
"vatId": "VAT123456",
"vatCountryCode": "DE",
"vatBusiness": true,
"vatReverseCharge": "false",
"defaultPrefix": "for"
}
""".formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorSuffix))
{
"debitorRelUuid": "%s",
"debitorNumberSuffix": "%s",
"defaultPrefix": "for",
"billable": "true",
"vatReverseCharge": "false"
}
""".formatted(givenDebitorRelUuid, ++nextDebitorSuffix))
.port(port)
.when()
.post("http://localhost/api/hs/office/debitors")
.then().log().all().assertThat()
.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
}
}
@ -321,14 +448,53 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.port(port)
.when()
.get("http://localhost/api/hs/office/debitors/" + givenDebitorUuid)
.then().log().body().assertThat()
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
{
"partner": { person: { "tradeName": "First GmbH" } },
"billingContact": { "label": "first contact" }
}
"debitorRel": {
"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
}
@ -350,7 +516,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
@Test
@Accepts({ "Debitor:X(Access Control)" })
void contactAdminUser_canGetRelatedDebitor() {
void contactAdminUser_canGetRelatedDebitorExceptRefundBankAccount() {
context.define("superuser-alex@hostsharing.net");
final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("first contact").get(0).getUuid();
@ -365,9 +531,10 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType("application/json")
.body("", lenientlyEquals("""
{
"partner": { person: { "tradeName": "First GmbH" } },
"billingContact": { "label": "first contact" },
"refundBankAccount": { "holder": "First GmbH" }
"debitorNumber": 1000111,
"partner": { "partnerNumber": 10001 },
"debitorRel": { "contact": { "label": "first contact" } },
"refundBankAccount": null
}
""")); // @formatter:on
}
@ -378,7 +545,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
class PatchDebitor {
@Test
void globalAdmin_withoutAssumedRole_canPatchAllPropertiesOfArbitraryDebitor() {
void globalAdmin_withoutAssumedRole_canPatchArbitraryDebitor() {
context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor();
@ -400,77 +567,90 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.port(port)
.when()
.patch("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid())
.then().assertThat()
.then().log().all().assertThat()
.statusCode(200)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("vatId", is("VAT222222"))
.body("vatCountryCode", is("AA"))
.body("vatBusiness", is(true))
.body("defaultPrefix", is("for"))
.body("billingContact.label", is(givenContact.getLabel()))
.body("partner.person.tradeName", is(givenDebitor.getPartner().getPerson().getTradeName()));
.body("", lenientlyEquals("""
{
"debitorRel": {
"anchor": { "tradeName": "Fourth eG" },
"holder": { "tradeName": "Fourth eG" },
"type": "DEBITOR",
"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
// finally, the debitor is actually updated
context.define("superuser-alex@hostsharing.net");
assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent().get()
.matches(partner -> {
assertThat(partner.getPartner().getPerson().getTradeName()).isEqualTo(givenDebitor.getPartner()
.getPerson()
.getTradeName());
assertThat(partner.getBillingContact().getLabel()).isEqualTo("fourth contact");
assertThat(partner.getVatId()).isEqualTo("VAT222222");
assertThat(partner.getVatCountryCode()).isEqualTo("AA");
assertThat(partner.isVatBusiness()).isEqualTo(true);
.matches(debitor -> {
assertThat(debitor.getDebitorRel().getHolder().getTradeName())
.isEqualTo(givenDebitor.getDebitorRel().getHolder().getTradeName());
assertThat(debitor.getDebitorRel().getContact().getLabel()).isEqualTo("fourth contact");
assertThat(debitor.getVatId()).isEqualTo("VAT222222");
assertThat(debitor.getVatCountryCode()).isEqualTo("AA");
assertThat(debitor.isVatBusiness()).isEqualTo(true);
return true;
});
}
@Test
void globalAdmin_withoutAssumedRole_canPatchPartialPropertiesOfArbitraryDebitor() {
void theContactOwner_canNotPatchARelatedDebitor() {
context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor();
final var newBillingContact = contactRepo.findContactByOptionalLabelLike("sixth").get(0);
final var location = RestAssured // @formatter:off
.given()
// @formatter:on
RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.header("assumed-roles", "hs_office_contact#fourthcontact.admin")
.contentType(ContentType.JSON)
.body("""
{
"billingContactUuid": "%s",
"vatId": "VAT999999"
}
""".formatted(newBillingContact.getUuid()))
{
"vatId": "VAT999999"
}
""")
.port(port)
.when()
.when()
.patch("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid())
.then().assertThat()
.statusCode(200)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("billingContact.label", is("sixth contact"))
.body("vatId", is("VAT999999"))
.body("vatCountryCode", is(givenDebitor.getVatCountryCode()))
.body("vatBusiness", is(givenDebitor.isVatBusiness()));
// @formatter:on
.then().log().all().assertThat()
.statusCode(403)
.body("message", containsString("ERROR: [403] Subject"))
.body("message", containsString("is not allowed to update hs_office_debitor uuid "));
// 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
@ -500,7 +680,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
void contactAdminUser_canNotDeleteRelatedDebitor() {
context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor();
assertThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("fourth contact");
assertThat(givenDebitor.getDebitorRel().getContact().getLabel()).isEqualTo("fourth contact");
RestAssured // @formatter:off
.given()
@ -520,7 +700,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
void normalUser_canNotDeleteUnrelatedDebitor() {
context.define("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor();
assertThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("fourth contact");
assertThat(givenDebitor.getDebitorRel().getContact().getLabel()).isEqualTo("fourth contact");
RestAssured // @formatter:off
.given()
@ -544,8 +724,14 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix(++nextDebitorSuffix)
.billable(true)
.partner(givenPartner)
.billingContact(givenContact)
.debitorRel(
HsOfficeRelationEntity.builder()
.type(DEBITOR)
.anchor(givenPartner.getPartnerRel().getHolder())
.holder(givenPartner.getPartnerRel().getHolder())
.contact(givenContact)
.build()
)
.defaultPrefix("abc")
.vatReverseCharge(false)
.build();

View File

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

View File

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

View File

@ -1,7 +1,8 @@
package net.hostsharing.hsadminng.hs.office.debitor;
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.partner.TestHsOfficePartner.TEST_PARTNER;
@ -13,7 +14,11 @@ public class TestHsOfficeDebitor {
public static final HsOfficeDebitorEntity TEST_DEBITOR = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix(DEFAULT_DEBITOR_SUFFIX)
.debitorRel(HsOfficeRelationEntity.builder()
.holder(HsOfficePersonEntity.builder().build())
.anchor(HsOfficePersonEntity.builder().build())
.contact(TEST_CONTACT)
.build())
.partner(TEST_PARTNER)
.billingContact(TEST_CONTACT)
.build();
}

View File

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

View File

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