From 907e27ec1924362806df560999b582927a80b9d5 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 12 Mar 2024 10:13:36 +0100 Subject: [PATCH 1/8] import-debitor-relationship (into intermediate data structure) (#22) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/22 Reviewed-by: Timotheus Pokorra --- .../office/debitor/HsOfficeDebitorEntity.java | 6 ++-- .../HsOfficeRelationshipType.java | 2 +- .../changelog/220-hs-office-relationship.sql | 2 +- .../hs/office/migration/ImportOfficeData.java | 34 +++++++++++++++---- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index 29a9452d..0f92c6af 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -116,7 +116,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { JOIN hs_office_relationship partnerRel ON partnerRel.uuid = partner.partnerRoleUUid AND partnerRel.relType = 'PARTNER' JOIN hs_office_relationship debitorRel - ON debitorRel.relAnchorUuid = partnerRel.relHolderUuid AND partnerRel.relType = 'ACCOUNTING' + ON debitorRel.relAnchorUuid = partnerRel.relHolderUuid AND partnerRel.relType = 'DEBITOR' WHERE debitorRel.uuid = debitor.debitorRelUuid) || to_char(debitorNumberSuffix, 'fm00') from hs_office_debitor as debitor @@ -137,7 +137,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { fetchedBySql(""" SELECT * FROM hs_office_relationship AS r - WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid + WHERE r.relType = 'DEBITOR' AND r.relHolderUuid = ${REF}.debitorRelUuid """), dependsOnColumn("debitorRelUuid")) .createPermission(DELETE).grantedTo("debitorRel", OWNER) @@ -148,7 +148,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { dependsOnColumn("refundBankAccountUuid"), fetchedBySql(""" SELECT * FROM hs_office_relationship AS r - WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid + WHERE r.relType = 'DEBITOR' AND r.relHolderUuid = ${REF}.debitorRelUuid """) ) .toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java index 2b9fe60c..57053b1c 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java @@ -6,7 +6,7 @@ public enum HsOfficeRelationshipType { EX_PARTNER, REPRESENTATIVE, VIP_CONTACT, - ACCOUNTING, + DEBITOR, OPERATIONS, SUBSCRIBER } diff --git a/src/main/resources/db/changelog/220-hs-office-relationship.sql b/src/main/resources/db/changelog/220-hs-office-relationship.sql index 44f9e500..a2abece1 100644 --- a/src/main/resources/db/changelog/220-hs-office-relationship.sql +++ b/src/main/resources/db/changelog/220-hs-office-relationship.sql @@ -9,8 +9,8 @@ CREATE TYPE HsOfficeRelationshipType AS ENUM ( 'PARTNER', 'EX_PARTNER', 'REPRESENTATIVE', + 'DEBITOR', 'VIP_CONTACT', - 'ACCOUNTING', 'OPERATIONS', 'SUBSCRIBER'); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java index 929aa919..0c86dc66 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java @@ -175,7 +175,7 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(1011) + @Order(1019) void verifyBusinessPartners() { assumeThatWeAreImportingControlledTestData(); @@ -220,6 +220,23 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1021) + void buildDebitorRelationships() { + debitors.forEach( (id, debitor) -> { + final var debitorRel = HsOfficeRelationshipEntity.builder() + .relType(HsOfficeRelationshipType.DEBITOR) + .relAnchor(debitor.getPartner().getPartnerRole().getRelHolder()) + .relHolder(debitor.getPartner().getPartnerRole().getRelHolder()) // just 1 debitor/partner in legacy hsadmin + .contact(debitor.getBillingContact()) + .build(); + if (debitorRel.getRelAnchor() != null && debitorRel.getRelHolder() != null && + debitorRel.getContact() != null ) { + relationships.put(relationshipId++, debitorRel); + } + }); + } + + @Test + @Order(1029) void verifyContacts() { assumeThatWeAreImportingControlledTestData(); @@ -289,8 +306,11 @@ public class ImportOfficeData extends ContextBasedTest { 2000013=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), 2000014=rel(relAnchor='?? Test PS', relType='OPERATIONS', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000015=rel(relAnchor='?? Test PS', relType='REPRESENTATIVE', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000016=rel(relAnchor='NP Mellies, Michael', relType='SUBSCRIBER', relMark='operations-announce', relHolder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga ') - } + 2000016=rel(relAnchor='NP Mellies, Michael', relType='SUBSCRIBER', relMark='operations-announce', relHolder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '), + 2000017=rel(relAnchor='NP Mellies, Michael', relType='DEBITOR', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000018=rel(relAnchor='LP JM GmbH', relType='DEBITOR', relHolder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), + 2000019=rel(relAnchor='?? Test PS', relType='DEBITOR', relHolder='?? Test PS', contact='Petra Schmidt , Test PS') + } """); } @@ -307,7 +327,7 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(1031) + @Order(1039) void verifySepaMandates() { assumeThatWeAreImportingControlledTestData(); @@ -339,7 +359,7 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(1041) + @Order(1049) void verifyCoopShares() { assumeThatWeAreImportingControlledTestData(); @@ -366,7 +386,7 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(1051) + @Order(1059) void verifyCoopAssets() { assumeThatWeAreImportingControlledTestData(); @@ -398,7 +418,7 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(2001) + @Order(2009) void removeEmptyRelationships() { assumeThatWeAreImportingControlledTestData(); From 3faf2ea99e725841610c389cb7d6d251c132e6cf Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 13 Mar 2024 15:01:24 +0100 Subject: [PATCH 2/8] rename partnerRole -> partnerRel, relationship -> relation and remove rel-Prefix (relAnchor etc.) (#23) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/23 Reviewed-by: Timotheus Pokorra --- doc/hs-office-data-structure.md | 85 +++--- .../office/debitor/HsOfficeDebitorEntity.java | 26 +- .../partner/HsOfficePartnerController.java | 24 +- .../partner/HsOfficePartnerDetailsEntity.java | 10 +- .../office/partner/HsOfficePartnerEntity.java | 16 +- .../HsOfficeRelationController.java} | 80 +++--- .../HsOfficeRelationEntity.java} | 62 ++--- .../HsOfficeRelationEntityPatcher.java} | 12 +- .../relation/HsOfficeRelationRepository.java | 37 +++ .../HsOfficeRelationType.java} | 4 +- .../HsOfficeRelationshipRepository.java | 37 --- .../HsOfficeSepaMandateEntity.java | 4 +- .../hsadminng/rbac/rbacdef/RbacView.java | 4 +- .../hs-office/api-mappings.yaml | 2 +- .../hs-office/hs-office-partner-schemas.yaml | 14 +- ....yaml => hs-office-relations-schemas.yaml} | 34 +-- ...aml => hs-office-relations-with-uuid.yaml} | 34 +-- ...ionships.yaml => hs-office-relations.yaml} | 28 +- .../api-definition/hs-office/hs-office.yaml | 10 +- .../db/changelog/220-hs-office-relation.sql | 36 +++ .../changelog/220-hs-office-relationship.sql | 36 --- ...rbac.md => 223-hs-office-relation-rbac.md} | 14 +- ...ac.sql => 223-hs-office-relation-rbac.sql} | 112 ++++---- ...l => 228-hs-office-relation-test-data.sql} | 44 +-- .../db/changelog/230-hs-office-partner.sql | 6 +- .../changelog/233-hs-office-partner-rbac.sql | 30 +-- .../238-hs-office-partner-test-data.sql | 20 +- .../db/changelog/db.changelog-master.yaml | 6 +- .../hsadminng/arch/ArchitectureTest.java | 12 +- .../hs/office/migration/ImportOfficeData.java | 130 ++++----- ...OfficePartnerControllerAcceptanceTest.java | 44 +-- .../HsOfficePartnerControllerRestTest.java | 38 +-- ...fficePartnerRepositoryIntegrationTest.java | 92 +++---- ...ficeRelationControllerAcceptanceTest.java} | 252 +++++++++--------- ...sOfficeRelationEntityPatcherUnitTest.java} | 36 +-- .../HsOfficeRelationEntityUnitTest.java | 43 +++ ...iceRelationRepositoryIntegrationTest.java} | 228 ++++++++-------- .../HsOfficeRelationshipEntityUnitTest.java | 44 --- tools/generate | 41 --- 39 files changed, 873 insertions(+), 914 deletions(-) rename src/main/java/net/hostsharing/hsadminng/hs/office/{relationship/HsOfficeRelationshipController.java => relation/HsOfficeRelationController.java} (51%) rename src/main/java/net/hostsharing/hsadminng/hs/office/{relationship/HsOfficeRelationshipEntity.java => relation/HsOfficeRelationEntity.java} (66%) rename src/main/java/net/hostsharing/hsadminng/hs/office/{relationship/HsOfficeRelationshipEntityPatcher.java => relation/HsOfficeRelationEntityPatcher.java} (67%) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepository.java rename src/main/java/net/hostsharing/hsadminng/hs/office/{relationship/HsOfficeRelationshipType.java => relation/HsOfficeRelationType.java} (56%) delete mode 100644 src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepository.java rename src/main/resources/api-definition/hs-office/{hs-office-relationship-schemas.yaml => hs-office-relations-schemas.yaml} (73%) rename src/main/resources/api-definition/hs-office/{hs-office-relationships-with-uuid.yaml => hs-office-relations-with-uuid.yaml} (64%) rename src/main/resources/api-definition/hs-office/{hs-office-relationships.yaml => hs-office-relations.yaml} (61%) create mode 100644 src/main/resources/db/changelog/220-hs-office-relation.sql delete mode 100644 src/main/resources/db/changelog/220-hs-office-relationship.sql rename src/main/resources/db/changelog/{223-hs-office-relationship-rbac.md => 223-hs-office-relation-rbac.md} (64%) rename src/main/resources/db/changelog/{223-hs-office-relationship-rbac.sql => 223-hs-office-relation-rbac.sql} (54%) rename src/main/resources/db/changelog/{228-hs-office-relationship-test-data.sql => 228-hs-office-relation-test-data.sql} (57%) rename src/test/java/net/hostsharing/hsadminng/hs/office/{relationship/HsOfficeRelationshipControllerAcceptanceTest.java => relation/HsOfficeRelationControllerAcceptanceTest.java} (63%) rename src/test/java/net/hostsharing/hsadminng/hs/office/{relationship/HsOfficeRelationshipEntityPatcherUnitTest.java => relation/HsOfficeRelationEntityPatcherUnitTest.java} (67%) create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityUnitTest.java rename src/test/java/net/hostsharing/hsadminng/hs/office/{relationship/HsOfficeRelationshipRepositoryIntegrationTest.java => relation/HsOfficeRelationRepositoryIntegrationTest.java} (54%) delete mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityUnitTest.java delete mode 100755 tools/generate diff --git a/doc/hs-office-data-structure.md b/doc/hs-office-data-structure.md index 960e572b..b84264d0 100644 --- a/doc/hs-office-data-structure.md +++ b/doc/hs-office-data-structure.md @@ -10,7 +10,7 @@ classDiagram namespace Partner { class partner-MeierGmbH - class role-MeierGmbH + class rel-MeierGmbH class personDetails-MeierGmbH class contactData-MeierGmbH class person-MeierGmbH @@ -19,28 +19,29 @@ classDiagram namespace Representatives { class person-FrankMeier class contactData-FrankMeier - class role-MeierGmbH-FrankMeier + class rel-MeierGmbH-FrankMeier } namespace Debitors { class debitor-MeierGmbH class contactData-MeierGmbH-Buha - class role-MeierGmbH-Buha + class rel-MeierGmbH-Buha } namespace Operations { class person-SabineMeier class contactData-SabineMeier - class role-MeierGmbH-SabineMeier + class rel-MeierGmbH-SabineMeier } namespace Enums { - class RoleType { + class RelationType { <> UNKNOWN + PARTNER + DEBITOR REPRESENTATIVE - ACCOUNTING OPERATIONS } @@ -64,9 +65,9 @@ classDiagram class partner-MeierGmbH { +Numeric partnerNumber: 12345 - +Role partnerRole + +Relation partnerRel } - partner-MeierGmbH *-- role-MeierGmbH + partner-MeierGmbH *-- rel-MeierGmbH class person-MeierGmbH { +personType: LEGAL @@ -90,32 +91,32 @@ classDiagram +emailAddresses: office@meier-gmbh.de } - class role-MeierGmbH { - +RoleType RoleType PARTNER + class rel-MeierGmbH { + +RelationType type PARTNER +Person anchor +Person holder - +Contact roleContact + +Contact contact } - role-MeierGmbH o-- person-HostsharingEG : anchor - role-MeierGmbH o-- person-MeierGmbH : holder - role-MeierGmbH o-- contactData-MeierGmbH + rel-MeierGmbH o-- person-HostsharingEG : anchor + rel-MeierGmbH o-- person-MeierGmbH : holder + rel-MeierGmbH o-- contactData-MeierGmbH %% --- Debitors --- class debitor-MeierGmbH { - +Partner partner - +Numeric[2] debitorNumberSuffix: 00 - +Role billingRole - +boolean billable: true - +String vatId: ID123456789 - +String vatCountryCode: DE - +boolean vatBusiness: true - +boolean vatReverseCharge: false + +Partner partner + +Numeric[2] debitorNumberSuffix: 00 + +Relation debitorRel + +boolean billable: true + +String vatId: ID123456789 + +String vatCountryCode: DE + +boolean vatBusiness: true + +boolean vatReverseCharge: false +BankAccount refundBankAccount - +String defaultPrefix: mei + +String defaultPrefix: mei } debitor-MeierGmbH o-- partner-MeierGmbH - debitor-MeierGmbH *-- role-MeierGmbH-Buha + debitor-MeierGmbH *-- rel-MeierGmbH-Buha class contactData-MeierGmbH-Buha { +postalAddress: Hauptstraße 5, 22345 Hamburg @@ -123,15 +124,15 @@ classDiagram +emailAddresses: buha@meier-gmbh.de } - class role-MeierGmbH-Buha { - +RoleType RoleType ACCOUNTING + class rel-MeierGmbH-Buha { + +RelationType type DEBITOR +Person anchor +Person holder - +Contact roleContact + +Contact contact } - role-MeierGmbH-Buha o-- person-MeierGmbH : anchor - role-MeierGmbH-Buha o-- person-MeierGmbH : holder - role-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha + rel-MeierGmbH-Buha o-- person-MeierGmbH : anchor + rel-MeierGmbH-Buha o-- person-MeierGmbH : holder + rel-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha %% --- Representatives --- @@ -148,15 +149,15 @@ classDiagram +emailAddresses: frank.meier@meier-gmbh.de } - class role-MeierGmbH-FrankMeier { - +RoleType RoleType REPRESENTATIVE + class rel-MeierGmbH-FrankMeier { + +RelationType type REPRESENTATIVE +Person anchor +Person holder - +Contact roleContact + +Contact contact } - role-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor - role-MeierGmbH-FrankMeier o-- person-FrankMeier : holder - role-MeierGmbH-FrankMeier o-- contactData-FrankMeier + rel-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor + rel-MeierGmbH-FrankMeier o-- person-FrankMeier : holder + rel-MeierGmbH-FrankMeier o-- contactData-FrankMeier %% --- Operations --- @@ -173,14 +174,14 @@ classDiagram +emailAddresses: sabine.meier@meier-gmbh.de } - class role-MeierGmbH-SabineMeier { - +RoleType RoleType OPERATIONAL + class rel-MeierGmbH-SabineMeier { + +RelationType type OPERATIONAL +Person anchor +Person holder - +Contact roleContact + +Contact contact } - role-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor - role-MeierGmbH-SabineMeier o-- person-SabineMeier : holder - role-MeierGmbH-SabineMeier o-- contactData-SabineMeier + rel-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor + rel-MeierGmbH-SabineMeier o-- person-SabineMeier : holder + rel-MeierGmbH-SabineMeier o-- contactData-SabineMeier ``` diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index 0f92c6af..66f82f95 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -5,7 +5,7 @@ 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.relationship.HsOfficeRelationshipEntity; +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; @@ -113,10 +113,10 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { SELECT debitor.uuid, 'D-' || (SELECT partner.partnerNumber FROM hs_office_partner partner - JOIN hs_office_relationship partnerRel - ON partnerRel.uuid = partner.partnerRoleUUid AND partnerRel.relType = 'PARTNER' - JOIN hs_office_relationship debitorRel - ON debitorRel.relAnchorUuid = partnerRel.relHolderUuid AND partnerRel.relType = 'DEBITOR' + 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 @@ -133,11 +133,11 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { "defaultPrefix" /* TODO: do we want that updatable? */) .createPermission(custom("new-debitor")).grantedTo("global", ADMIN) - .importRootEntityAliasProxy("debitorRel", HsOfficeRelationshipEntity.class, + .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, fetchedBySql(""" SELECT * - FROM hs_office_relationship AS r - WHERE r.relType = 'DEBITOR' AND r.relHolderUuid = ${REF}.debitorRelUuid + FROM hs_office_relation AS r + WHERE r.type = 'DEBITOR' AND r.holderUuid = ${REF}.debitorRelUuid """), dependsOnColumn("debitorRelUuid")) .createPermission(DELETE).grantedTo("debitorRel", OWNER) @@ -147,18 +147,18 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { .importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class, dependsOnColumn("refundBankAccountUuid"), fetchedBySql(""" SELECT * - FROM hs_office_relationship AS r - WHERE r.relType = 'DEBITOR' AND r.relHolderUuid = ${REF}.debitorRelUuid + FROM hs_office_relation AS r + WHERE r.type = 'DEBITOR' AND r.holderUuid = ${REF}.debitorRelUuid """) ) .toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT) .toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER) - .importEntityAlias("partnerRel", HsOfficeRelationshipEntity.class, + .importEntityAlias("partnerRel", HsOfficeRelationEntity.class, dependsOnColumn("partnerRelUuid"), fetchedBySql(""" SELECT * - FROM hs_office_relationship AS partnerRel - WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid + FROM hs_office_relation AS partnerRel + WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid """) ) .toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java index 6fdd0732..7a3813b9 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java @@ -7,11 +7,11 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePartners import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerResource; -import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerRoleInsertResource; +import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerRelInsertResource; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType; import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import org.springframework.beans.factory.annotation.Autowired; @@ -40,7 +40,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi { private HsOfficePartnerRepository partnerRepo; @Autowired - private HsOfficeRelationshipRepository relationshipRepo; + private HsOfficeRelationRepository relationRepo; @PersistenceContext private EntityManager em; @@ -112,7 +112,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi { if (partnerRepo.deleteByUuid(partnerUuid) != 1 || // TODO: move to after delete trigger in partner - relationshipRepo.deleteByUuid(partnerToDelete.get().getPartnerRole().getUuid()) != 1 ) { + relationRepo.deleteByUuid(partnerToDelete.get().getPartnerRel().getUuid()) != 1 ) { return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } @@ -141,18 +141,18 @@ public class HsOfficePartnerController implements HsOfficePartnersApi { private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) { final var entityToSave = new HsOfficePartnerEntity(); entityToSave.setPartnerNumber(body.getPartnerNumber()); - entityToSave.setPartnerRole(persistPartnerRole(body.getPartnerRole())); + 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; } - private HsOfficeRelationshipEntity persistPartnerRole(final HsOfficePartnerRoleInsertResource resource) { - final var entity = new HsOfficeRelationshipEntity(); - entity.setRelType(HsOfficeRelationshipType.PARTNER); - entity.setRelAnchor(ref(HsOfficePersonEntity.class, resource.getRelAnchorUuid())); - entity.setRelHolder(ref(HsOfficePersonEntity.class, resource.getRelHolderUuid())); + private HsOfficeRelationEntity persistPartnerRel(final HsOfficePartnerRelInsertResource resource) { + final var entity = new HsOfficeRelationEntity(); + entity.setType(HsOfficeRelationType.PARTNER); + entity.setAnchor(ref(HsOfficePersonEntity.class, resource.getAnchorUuid())); + entity.setHolder(ref(HsOfficePersonEntity.class, resource.getHolderUuid())); entity.setContact(ref(HsOfficeContactEntity.class, resource.getContactUuid())); em.persist(entity); return entity; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java index e557f9ae..435357fe 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java @@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.hs.office.partner; import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; +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; @@ -86,15 +86,15 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable { "dateOfDeath") .createPermission(custom("new-partner-details")).grantedTo("global", ADMIN) - .importRootEntityAliasProxy("partnerRel", HsOfficeRelationshipEntity.class, + .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, fetchedBySql(""" SELECT partnerRel.* - FROM hs_office_relationship AS partnerRel + FROM hs_office_relation AS partnerRel JOIN hs_office_partner AS partner ON partner.detailsUuid = ${ref}.uuid - WHERE partnerRel.uuid = partner.partnerRoleUuid + WHERE partnerRel.uuid = partner.partnerRelUuid """), - dependsOnColumn("partnerRoleUuid")) + dependsOnColumn("partnerRelUuid")) // The grants are defined in HsOfficePartnerEntity.rbac() // because they have to be changed when its partnerRel changes, diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java index aa000f67..8e35e9b0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java @@ -5,7 +5,7 @@ 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.hs.office.relationship.HsOfficeRelationshipEntity; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.stringify.Stringify; @@ -50,15 +50,15 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid { private Integer partnerNumber; @ManyToOne - @JoinColumn(name = "partnerroleuuid", nullable = false) - private HsOfficeRelationshipEntity partnerRole; + @JoinColumn(name = "partnerreluuid", nullable = false) + private HsOfficeRelationEntity partnerRel; - // TODO: remove, is replaced by partnerRole + // TODO: remove, is replaced by partnerRel @ManyToOne @JoinColumn(name = "personuuid", nullable = false) private HsOfficePersonEntity person; - // TODO: remove, is replaced by partnerRole + // TODO: remove, is replaced by partnerRel @ManyToOne @JoinColumn(name = "contactuuid", nullable = false) private HsOfficeContactEntity contact; @@ -87,13 +87,13 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid { FROM hs_office_partner AS partner """)) .withUpdatableColumns( - "partnerRoleUuid", + "partnerRelUuid", "personUuid", "contactUuid") .createPermission(custom("new-partner")).grantedTo("global", ADMIN) - .importRootEntityAliasProxy("partnerRel", HsOfficeRelationshipEntity.class, - fetchedBySql("SELECT * FROM hs_office_relationship AS r WHERE r.uuid = ${ref}.partnerRoleUuid"), + .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, + fetchedBySql("SELECT * FROM hs_office_relation AS r WHERE r.uuid = ${ref}.partnerRelUuid"), dependsOnColumn("partnerRelUuid")) .createPermission(DELETE).grantedTo("partnerRel", ADMIN) .createPermission(UPDATE).grantedTo("partnerRel", AGENT) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java similarity index 51% rename from src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipController.java rename to src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java index 98c6bccf..a7923128 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java @@ -1,8 +1,8 @@ -package net.hostsharing.hsadminng.hs.office.relationship; +package net.hostsharing.hsadminng.hs.office.relation; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; -import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationshipsApi; +import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; import net.hostsharing.hsadminng.mapper.Mapper; @@ -22,7 +22,7 @@ import java.util.function.BiConsumer; @RestController -public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi { +public class HsOfficeRelationController implements HsOfficeRelationsApi { @Autowired private Context context; @@ -31,10 +31,10 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi private Mapper mapper; @Autowired - private HsOfficeRelationshipRepository relationshipRepo; + private HsOfficeRelationRepository relationRepo; @Autowired - private HsOfficePersonRepository relHolderRepo; + private HsOfficePersonRepository holderRepo; @Autowired private HsOfficeContactRepository contactRepo; @@ -44,79 +44,79 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi @Override @Transactional(readOnly = true) - public ResponseEntity> listRelationships( + public ResponseEntity> listRelations( final String currentUser, final String assumedRoles, final UUID personUuid, - final HsOfficeRelationshipTypeResource relationshipType) { + final HsOfficeRelationTypeResource relationType) { context.define(currentUser, assumedRoles); - final var entities = relationshipRepo.findRelationshipRelatedToPersonUuidAndRelationshipType(personUuid, - mapper.map(relationshipType, HsOfficeRelationshipType.class)); + final var entities = relationRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid, + mapper.map(relationType, HsOfficeRelationType.class)); - final var resources = mapper.mapList(entities, HsOfficeRelationshipResource.class, - RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER); + final var resources = mapper.mapList(entities, HsOfficeRelationResource.class, + RELATION_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(resources); } @Override @Transactional - public ResponseEntity addRelationship( + public ResponseEntity addRelation( final String currentUser, final String assumedRoles, - final HsOfficeRelationshipInsertResource body) { + final HsOfficeRelationInsertResource body) { context.define(currentUser, assumedRoles); - final var entityToSave = new HsOfficeRelationshipEntity(); - entityToSave.setRelType(HsOfficeRelationshipType.valueOf(body.getRelType())); - entityToSave.setRelAnchor(relHolderRepo.findByUuid(body.getRelAnchorUuid()).orElseThrow( - () -> new NoSuchElementException("cannot find relAnchorUuid " + body.getRelAnchorUuid()) + final var entityToSave = new HsOfficeRelationEntity(); + entityToSave.setType(HsOfficeRelationType.valueOf(body.getType())); + entityToSave.setAnchor(holderRepo.findByUuid(body.getAnchorUuid()).orElseThrow( + () -> new NoSuchElementException("cannot find anchorUuid " + body.getAnchorUuid()) )); - entityToSave.setRelHolder(relHolderRepo.findByUuid(body.getRelHolderUuid()).orElseThrow( - () -> new NoSuchElementException("cannot find relHolderUuid " + body.getRelHolderUuid()) + entityToSave.setHolder(holderRepo.findByUuid(body.getHolderUuid()).orElseThrow( + () -> new NoSuchElementException("cannot find holderUuid " + body.getHolderUuid()) )); entityToSave.setContact(contactRepo.findByUuid(body.getContactUuid()).orElseThrow( () -> new NoSuchElementException("cannot find contactUuid " + body.getContactUuid()) )); - final var saved = relationshipRepo.save(entityToSave); + final var saved = relationRepo.save(entityToSave); final var uri = MvcUriComponentsBuilder.fromController(getClass()) - .path("/api/hs/office/relationships/{id}") + .path("/api/hs/office/relations/{id}") .buildAndExpand(saved.getUuid()) .toUri(); - final var mapped = mapper.map(saved, HsOfficeRelationshipResource.class, - RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER); + final var mapped = mapper.map(saved, HsOfficeRelationResource.class, + RELATION_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.created(uri).body(mapped); } @Override @Transactional(readOnly = true) - public ResponseEntity getRelationshipByUuid( + public ResponseEntity getRelationByUuid( final String currentUser, final String assumedRoles, - final UUID relationshipUuid) { + final UUID relationUuid) { context.define(currentUser, assumedRoles); - final var result = relationshipRepo.findByUuid(relationshipUuid); + final var result = relationRepo.findByUuid(relationUuid); if (result.isEmpty()) { return ResponseEntity.notFound().build(); } - return ResponseEntity.ok(mapper.map(result.get(), HsOfficeRelationshipResource.class, RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER)); + return ResponseEntity.ok(mapper.map(result.get(), HsOfficeRelationResource.class, RELATION_ENTITY_TO_RESOURCE_POSTMAPPER)); } @Override @Transactional - public ResponseEntity deleteRelationshipByUuid( + public ResponseEntity deleteRelationByUuid( final String currentUser, final String assumedRoles, - final UUID relationshipUuid) { + final UUID relationUuid) { context.define(currentUser, assumedRoles); - final var result = relationshipRepo.deleteByUuid(relationshipUuid); + final var result = relationRepo.deleteByUuid(relationUuid); if (result == 0) { return ResponseEntity.notFound().build(); } @@ -126,27 +126,27 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi @Override @Transactional - public ResponseEntity patchRelationship( + public ResponseEntity patchRelation( final String currentUser, final String assumedRoles, - final UUID relationshipUuid, - final HsOfficeRelationshipPatchResource body) { + final UUID relationUuid, + final HsOfficeRelationPatchResource body) { context.define(currentUser, assumedRoles); - final var current = relationshipRepo.findByUuid(relationshipUuid).orElseThrow(); + final var current = relationRepo.findByUuid(relationUuid).orElseThrow(); - new HsOfficeRelationshipEntityPatcher(em, current).apply(body); + new HsOfficeRelationEntityPatcher(em, current).apply(body); - final var saved = relationshipRepo.save(current); - final var mapped = mapper.map(saved, HsOfficeRelationshipResource.class); + final var saved = relationRepo.save(current); + final var mapped = mapper.map(saved, HsOfficeRelationResource.class); return ResponseEntity.ok(mapped); } - final BiConsumer RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { - resource.setRelAnchor(mapper.map(entity.getRelAnchor(), HsOfficePersonResource.class)); - resource.setRelHolder(mapper.map(entity.getRelHolder(), HsOfficePersonResource.class)); + final BiConsumer RELATION_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { + resource.setAnchor(mapper.map(entity.getAnchor(), HsOfficePersonResource.class)); + resource.setHolder(mapper.map(entity.getHolder(), HsOfficePersonResource.class)); resource.setContact(mapper.map(entity.getContact(), HsOfficeContactResource.class)); }; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java similarity index 66% rename from src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java rename to src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java index 1ec9fd74..71e2b11a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java @@ -1,4 +1,4 @@ -package net.hostsharing.hsadminng.hs.office.relationship; +package net.hostsharing.hsadminng.hs.office.relation; import lombok.*; import lombok.experimental.FieldNameConstants; @@ -24,49 +24,49 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Entity -@Table(name = "hs_office_relationship_rv") +@Table(name = "hs_office_relation_rv") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @FieldNameConstants -public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable { +public class HsOfficeRelationEntity implements HasUuid, Stringifyable { - private static Stringify toString = stringify(HsOfficeRelationshipEntity.class, "rel") - .withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor) - .withProp(Fields.relType, HsOfficeRelationshipEntity::getRelType) - .withProp(Fields.relMark, HsOfficeRelationshipEntity::getRelMark) - .withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder) - .withProp(Fields.contact, HsOfficeRelationshipEntity::getContact); + private static Stringify toString = stringify(HsOfficeRelationEntity.class, "rel") + .withProp(Fields.anchor, HsOfficeRelationEntity::getAnchor) + .withProp(Fields.type, HsOfficeRelationEntity::getType) + .withProp(Fields.mark, HsOfficeRelationEntity::getMark) + .withProp(Fields.holder, HsOfficeRelationEntity::getHolder) + .withProp(Fields.contact, HsOfficeRelationEntity::getContact); - private static Stringify toShortString = stringify(HsOfficeRelationshipEntity.class, "rel") - .withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor) - .withProp(Fields.relType, HsOfficeRelationshipEntity::getRelType) - .withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder); + private static Stringify toShortString = stringify(HsOfficeRelationEntity.class, "rel") + .withProp(Fields.anchor, HsOfficeRelationEntity::getAnchor) + .withProp(Fields.type, HsOfficeRelationEntity::getType) + .withProp(Fields.holder, HsOfficeRelationEntity::getHolder); @Id @GeneratedValue private UUID uuid; @ManyToOne - @JoinColumn(name = "relanchoruuid") - private HsOfficePersonEntity relAnchor; + @JoinColumn(name = "anchoruuid") + private HsOfficePersonEntity anchor; @ManyToOne - @JoinColumn(name = "relholderuuid") - private HsOfficePersonEntity relHolder; + @JoinColumn(name = "holderuuid") + private HsOfficePersonEntity holder; @ManyToOne @JoinColumn(name = "contactuuid") private HsOfficeContactEntity contact; - @Column(name = "reltype") + @Column(name = "type") @Enumerated(EnumType.STRING) - private HsOfficeRelationshipType relType; + private HsOfficeRelationType type; - @Column(name = "relmark") - private String relMark; + @Column(name = "mark") + private String mark; @Override public String toString() { @@ -79,22 +79,22 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable { } public static RbacView rbac() { - return rbacViewFor("relationship", HsOfficeRelationshipEntity.class) + return rbacViewFor("relation", HsOfficeRelationEntity.class) .withIdentityView(SQL.projection(""" - (select idName from hs_office_person_iv p where p.uuid = relAnchorUuid) - || '-with-' || target.relType || '-' - || (select idName from hs_office_person_iv p where p.uuid = relHolderUuid) + (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) """)) .withRestrictedViewOrderBy(SQL.expression( - "(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)")) + "(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)")) .withUpdatableColumns("contactUuid") .importEntityAlias("anchorPerson", HsOfficePersonEntity.class, - dependsOnColumn("relAnchorUuid"), - fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relAnchorUuid") + dependsOnColumn("anchorUuid"), + fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid") ) .importEntityAlias("holderPerson", HsOfficePersonEntity.class, - dependsOnColumn("relHolderUuid"), - fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relHolderUuid") + dependsOnColumn("holderUuid"), + fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid") ) .importEntityAlias("contact", HsOfficeContactEntity.class, dependsOnColumn("contactUuid"), @@ -123,6 +123,6 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable { } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("223-hs-office-relationship-rbac-generated"); + rbac().generateWithBaseFileName("223-hs-office-relation-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcher.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityPatcher.java similarity index 67% rename from src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcher.java rename to src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityPatcher.java index fa080ba2..aeaae5ea 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcher.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityPatcher.java @@ -1,25 +1,25 @@ -package net.hostsharing.hsadminng.hs.office.relationship; +package net.hostsharing.hsadminng.hs.office.relation; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; -import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationshipPatchResource; +import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource; import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.OptionalFromJson; import jakarta.persistence.EntityManager; import java.util.UUID; -class HsOfficeRelationshipEntityPatcher implements EntityPatcher { +class HsOfficeRelationEntityPatcher implements EntityPatcher { private final EntityManager em; - private final HsOfficeRelationshipEntity entity; + private final HsOfficeRelationEntity entity; - HsOfficeRelationshipEntityPatcher(final EntityManager em, final HsOfficeRelationshipEntity entity) { + HsOfficeRelationEntityPatcher(final EntityManager em, final HsOfficeRelationEntity entity) { this.em = em; this.entity = entity; } @Override - public void apply(final HsOfficeRelationshipPatchResource resource) { + public void apply(final HsOfficeRelationPatchResource resource) { OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> { verifyNotNull(newValue, "contact"); entity.setContact(em.getReference(HsOfficeContactEntity.class, newValue)); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepository.java new file mode 100644 index 00000000..95bac3a2 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepository.java @@ -0,0 +1,37 @@ +package net.hostsharing.hsadminng.hs.office.relation; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; + +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface HsOfficeRelationRepository extends Repository { + + Optional findByUuid(UUID id); + + default List findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) { + return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType.toString()); + } + + @Query(value = """ + SELECT p.* FROM hs_office_relation_rv AS p + WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid + """, nativeQuery = true) + List findRelationRelatedToPersonUuid(@NotNull UUID personUuid); + + @Query(value = """ + SELECT p.* FROM hs_office_relation_rv AS p + WHERE (:relationType IS NULL OR p.type = cast(:relationType AS HsOfficeRelationType)) + AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid) + """, nativeQuery = true) + List findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType); + + HsOfficeRelationEntity save(final HsOfficeRelationEntity entity); + + long count(); + + int deleteByUuid(UUID uuid); +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationType.java similarity index 56% rename from src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java rename to src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationType.java index 57053b1c..035c9b55 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationType.java @@ -1,6 +1,6 @@ -package net.hostsharing.hsadminng.hs.office.relationship; +package net.hostsharing.hsadminng.hs.office.relation; -public enum HsOfficeRelationshipType { +public enum HsOfficeRelationType { UNKNOWN, PARTNER, EX_PARTNER, diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepository.java deleted file mode 100644 index d34caa8c..00000000 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepository.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.hostsharing.hsadminng.hs.office.relationship; - -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.Repository; - -import jakarta.validation.constraints.NotNull; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -public interface HsOfficeRelationshipRepository extends Repository { - - Optional findByUuid(UUID id); - - default List findRelationshipRelatedToPersonUuidAndRelationshipType(@NotNull UUID personUuid, HsOfficeRelationshipType relationshipType) { - return findRelationshipRelatedToPersonUuidAndRelationshipTypeString(personUuid, relationshipType.toString()); - } - - @Query(value = """ - SELECT p.* FROM hs_office_relationship_rv AS p - WHERE p.relAnchorUuid = :personUuid OR p.relHolderUuid = :personUuid - """, nativeQuery = true) - List findRelationshipRelatedToPersonUuid(@NotNull UUID personUuid); - - @Query(value = """ - SELECT p.* FROM hs_office_relationship_rv AS p - WHERE (:relationshipType IS NULL OR p.relType = cast(:relationshipType AS HsOfficeRelationshipType)) - AND ( p.relAnchorUuid = :personUuid OR p.relHolderUuid = :personUuid) - """, nativeQuery = true) - List findRelationshipRelatedToPersonUuidAndRelationshipTypeString(@NotNull UUID personUuid, String relationshipType); - - HsOfficeRelationshipEntity save(final HsOfficeRelationshipEntity entity); - - long count(); - - int deleteByUuid(UUID uuid); -} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java index 7fcef622..1b8135ba 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java @@ -6,7 +6,7 @@ import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; +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.stringify.Stringify; @@ -99,7 +99,7 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid { .withIdentityView(projection("concat(tradeName, familyName, givenName)")) .withUpdatableColumns("reference", "agreement", "validity") - .importEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, dependsOnColumn("debitorRelUuid")) + .importEntityAlias("debitorRel", HsOfficeRelationEntity.class, dependsOnColumn("debitorRelUuid")) .importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, dependsOnColumn("bankAccountUuid")) .createRole(OWNER, (with) -> { diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 28d29365..2d5cd93c 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; 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.relationship.HsOfficeRelationshipEntity; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity; import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; @@ -804,7 +804,7 @@ public class RbacView { HsOfficePartnerDetailsEntity.class, HsOfficeBankAccountEntity.class, HsOfficeDebitorEntity.class, - HsOfficeRelationshipEntity.class, + HsOfficeRelationEntity.class, HsOfficeCoopAssetsTransactionEntity.class, HsOfficeContactEntity.class, HsOfficeSepaMandateEntity.class, diff --git a/src/main/resources/api-definition/hs-office/api-mappings.yaml b/src/main/resources/api-definition/hs-office/api-mappings.yaml index 11778eb0..2403e1e4 100644 --- a/src/main/resources/api-definition/hs-office/api-mappings.yaml +++ b/src/main/resources/api-definition/hs-office/api-mappings.yaml @@ -23,7 +23,7 @@ map: null: org.openapitools.jackson.nullable.JsonNullable /api/hs/office/persons/{personUUID}: null: org.openapitools.jackson.nullable.JsonNullable - /api/hs/office/relationships/{relationshipUUID}: + /api/hs/office/relations/{relationUUID}: null: org.openapitools.jackson.nullable.JsonNullable /api/hs/office/bankaccounts/{bankAccountUUID}: null: org.openapitools.jackson.nullable.JsonNullable diff --git a/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml index a473bd49..eb544c8d 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml @@ -96,8 +96,8 @@ components: format: int8 minimum: 10000 maximum: 99999 - partnerRole: - $ref: '#/components/schemas/HsOfficePartnerRoleInsert' + partnerRel: + $ref: '#/components/schemas/HsOfficePartnerRelInsert' personUuid: type: string format: uuid @@ -112,22 +112,22 @@ components: - contactUuid - details - HsOfficePartnerRoleInsert: + HsOfficePartnerRelInsert: type: object nullable: false properties: - relAnchorUuid: + anchorUuid: type: string format: uuid - relHolderUuid: + holderUuid: type: string format: uuid contactUuid: type: string format: uuid required: - - relAnchorUuid - - relHolderUuid + - anchorUuid + - holderUuid - relContactUuid HsOfficePartnerDetailsInsert: diff --git a/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-relations-schemas.yaml similarity index 73% rename from src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml rename to src/main/resources/api-definition/hs-office/hs-office-relations-schemas.yaml index af5e5f86..b092cd0a 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-relations-schemas.yaml @@ -3,37 +3,37 @@ components: schemas: - HsOfficeRelationshipType: + HsOfficeRelationType: type: string enum: - UNKNOWN - PARTNER - EX_PARTNER - - REPRESENTATIVE, + - DEBITOR + - REPRESENTATIVE - VIP_CONTACT - - ACCOUNTING, - OPERATIONS - SUBSCRIBER - HsOfficeRelationship: + HsOfficeRelation: type: object properties: uuid: type: string format: uuid - relAnchor: + anchor: $ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson' - relHolder: + holder: $ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson' - relType: + type: type: string - relMark: + mark: type: string nullable: true contact: $ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact' - HsOfficeRelationshipPatch: + HsOfficeRelationPatch: type: object properties: contactUuid: @@ -41,25 +41,25 @@ components: format: uuid nullable: true - HsOfficeRelationshipInsert: + HsOfficeRelationInsert: type: object properties: - relAnchorUuid: + anchorUuid: type: string format: uuid - relHolderUuid: + holderUuid: type: string format: uuid - relType: + type: type: string nullable: true - relMark: + mark: type: string contactUuid: type: string format: uuid required: - - relAnchorUuid - - relHolderUuid - - relType + - anchorUuid + - holderUuid + - type - relContactUuid diff --git a/src/main/resources/api-definition/hs-office/hs-office-relationships-with-uuid.yaml b/src/main/resources/api-definition/hs-office/hs-office-relations-with-uuid.yaml similarity index 64% rename from src/main/resources/api-definition/hs-office/hs-office-relationships-with-uuid.yaml rename to src/main/resources/api-definition/hs-office/hs-office-relations-with-uuid.yaml index d3b9605e..4511b895 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-relationships-with-uuid.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-relations-with-uuid.yaml @@ -1,25 +1,25 @@ get: tags: - - hs-office-relationships - description: 'Fetch a single person relationship by its uuid, if visible for the current subject.' - operationId: getRelationshipByUuid + - hs-office-relations + description: 'Fetch a single person relation by its uuid, if visible for the current subject.' + operationId: getRelationByUuid parameters: - $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/assumedRoles' - - name: relationshipUUID + - name: relationUUID in: path required: true schema: type: string format: uuid - description: UUID of the relationship to fetch. + description: UUID of the relation to fetch. responses: "200": description: OK content: 'application/json': schema: - $ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationship' + $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation' "401": $ref: './error-responses.yaml#/components/responses/Unauthorized' @@ -28,13 +28,13 @@ get: patch: tags: - - hs-office-relationships - description: 'Updates a single person relationship by its uuid, if permitted for the current subject.' - operationId: patchRelationship + - hs-office-relations + description: 'Updates a single person relation by its uuid, if permitted for the current subject.' + operationId: patchRelation parameters: - $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/assumedRoles' - - name: relationshipUUID + - name: relationUUID in: path required: true schema: @@ -44,14 +44,14 @@ patch: content: 'application/json': schema: - $ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationshipPatch' + $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationPatch' responses: "200": description: OK content: 'application/json': schema: - $ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationship' + $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation' "401": $ref: './error-responses.yaml#/components/responses/Unauthorized' "403": @@ -59,19 +59,19 @@ patch: delete: tags: - - hs-office-relationships - description: 'Delete a single person relationship by its uuid, if permitted for the current subject.' - operationId: deleteRelationshipByUuid + - hs-office-relations + description: 'Delete a single person relation by its uuid, if permitted for the current subject.' + operationId: deleteRelationByUuid parameters: - $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/assumedRoles' - - name: relationshipUUID + - name: relationUUID in: path required: true schema: type: string format: uuid - description: UUID of the relationship to delete. + description: UUID of the relation to delete. responses: "204": description: No Content diff --git a/src/main/resources/api-definition/hs-office/hs-office-relationships.yaml b/src/main/resources/api-definition/hs-office/hs-office-relations.yaml similarity index 61% rename from src/main/resources/api-definition/hs-office/hs-office-relationships.yaml rename to src/main/resources/api-definition/hs-office/hs-office-relations.yaml index 2d7ed2fd..6328974f 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-relationships.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-relations.yaml @@ -1,9 +1,9 @@ get: - summary: Returns a list of (optionally filtered) person relationships for a given person. - description: Returns the list of (optionally filtered) person relationships of a given person and which are visible to the current user or any of it's assumed roles. + summary: Returns a list of (optionally filtered) person relations for a given person. + description: Returns the list of (optionally filtered) person relations of a given person and which are visible to the current user or any of it's assumed roles. tags: - - hs-office-relationships - operationId: listRelationships + - hs-office-relations + operationId: listRelations parameters: - $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/assumedRoles' @@ -13,13 +13,13 @@ get: schema: type: string format: uuid - description: Prefix of name properties from relHolder or contact to filter the results. - - name: relationshipType + description: Prefix of name properties from holder or contact to filter the results. + - name: relationType in: query required: false schema: - $ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationshipType' - description: Prefix of name properties from relHolder or contact to filter the results. + $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationType' + description: Prefix of name properties from holder or contact to filter the results. responses: "200": description: OK @@ -28,17 +28,17 @@ get: schema: type: array items: - $ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationship' + $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation' "401": $ref: './error-responses.yaml#/components/responses/Unauthorized' "403": $ref: './error-responses.yaml#/components/responses/Forbidden' post: - summary: Adds a new person relationship. + summary: Adds a new person relation. tags: - - hs-office-relationships - operationId: addRelationship + - hs-office-relations + operationId: addRelation parameters: - $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/assumedRoles' @@ -46,7 +46,7 @@ post: content: 'application/json': schema: - $ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationshipInsert' + $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationInsert' required: true responses: "201": @@ -54,7 +54,7 @@ post: content: 'application/json': schema: - $ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationship' + $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation' "401": $ref: './error-responses.yaml#/components/responses/Unauthorized' "403": diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index f3110867..3bbc5c34 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -35,13 +35,13 @@ paths: $ref: "./hs-office-persons-with-uuid.yaml" - # Relationships + # Relations - /api/hs/office/relationships: - $ref: "./hs-office-relationships.yaml" + /api/hs/office/relations: + $ref: "./hs-office-relations.yaml" - /api/hs/office/relationships/{relationshipUUID}: - $ref: "./hs-office-relationships-with-uuid.yaml" + /api/hs/office/relations/{relationUUID}: + $ref: "./hs-office-relations-with-uuid.yaml" # BankAccounts diff --git a/src/main/resources/db/changelog/220-hs-office-relation.sql b/src/main/resources/db/changelog/220-hs-office-relation.sql new file mode 100644 index 00000000..8e6e56a1 --- /dev/null +++ b/src/main/resources/db/changelog/220-hs-office-relation.sql @@ -0,0 +1,36 @@ +--liquibase formatted sql + +-- ============================================================================ +--changeset hs-office-relation-MAIN-TABLE:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +CREATE TYPE HsOfficeRelationType AS ENUM ( + 'UNKNOWN', + 'PARTNER', + 'EX_PARTNER', + 'REPRESENTATIVE', + 'DEBITOR', + 'VIP_CONTACT', + 'OPERATIONS', + 'SUBSCRIBER'); + +CREATE CAST (character varying as HsOfficeRelationType) WITH INOUT AS IMPLICIT; + +create table if not exists hs_office_relation +( + uuid uuid unique references RbacObject (uuid) initially deferred, -- on delete cascade + anchorUuid uuid not null references hs_office_person(uuid), + holderUuid uuid not null references hs_office_person(uuid), + contactUuid uuid references hs_office_contact(uuid), + type HsOfficeRelationType not null, + mark varchar(24) +); +--// + + +-- ============================================================================ +--changeset hs-office-relation-MAIN-TABLE-JOURNAL:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +call create_journal('hs_office_relation'); +--// diff --git a/src/main/resources/db/changelog/220-hs-office-relationship.sql b/src/main/resources/db/changelog/220-hs-office-relationship.sql deleted file mode 100644 index a2abece1..00000000 --- a/src/main/resources/db/changelog/220-hs-office-relationship.sql +++ /dev/null @@ -1,36 +0,0 @@ ---liquibase formatted sql - --- ============================================================================ ---changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--// --- ---------------------------------------------------------------------------- - -CREATE TYPE HsOfficeRelationshipType AS ENUM ( - 'UNKNOWN', - 'PARTNER', - 'EX_PARTNER', - 'REPRESENTATIVE', - 'DEBITOR', - 'VIP_CONTACT', - 'OPERATIONS', - 'SUBSCRIBER'); - -CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT; - -create table if not exists hs_office_relationship -( - uuid uuid unique references RbacObject (uuid) initially deferred, -- on delete cascade - relAnchorUuid uuid not null references hs_office_person(uuid), - relHolderUuid uuid not null references hs_office_person(uuid), - contactUuid uuid references hs_office_contact(uuid), - relType HsOfficeRelationshipType not null, - relMark varchar(24) -); ---// - - --- ============================================================================ ---changeset hs-office-relationship-MAIN-TABLE-JOURNAL:1 endDelimiter:--// --- ---------------------------------------------------------------------------- - -call create_journal('hs_office_relationship'); ---// diff --git a/src/main/resources/db/changelog/223-hs-office-relationship-rbac.md b/src/main/resources/db/changelog/223-hs-office-relation-rbac.md similarity index 64% rename from src/main/resources/db/changelog/223-hs-office-relationship-rbac.md rename to src/main/resources/db/changelog/223-hs-office-relation-rbac.md index 8ffa55ff..40691f38 100644 --- a/src/main/resources/db/changelog/223-hs-office-relationship-rbac.md +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac.md @@ -1,4 +1,4 @@ -### hs_office_relationship RBAC +### hs_office_relation RBAC ```mermaid @@ -28,17 +28,17 @@ subgraph hsOfficePerson --> role:hsOfficePerson.guest[person.guest] end -subgraph hsOfficeRelationship +subgraph hsOfficeRelation - role:hsOfficePerson#relAnchor.admin[person#anchor.admin] + role:hsOfficePerson#anchor.admin[person#anchor.admin] --- role:hsOfficePerson.admin - role:hsOfficeRelationship.owner[relationship.owner] + role:hsOfficeRelation.owner[relation.owner] %% permissions - role:hsOfficeRelationship.owner --> perm:hsOfficeRelationship.*{{relationship.*}} + role:hsOfficeRelation.owner --> perm:hsOfficeRelation.*{{relation.*}} %% incoming - role:global.admin ---> role:hsOfficeRelationship.owner - role:hsOfficePersonAdmin#relAnchor.admin + role:global.admin ---> role:hsOfficeRelation.owner + role:hsOfficePersonAdmin#anchor.admin end ``` diff --git a/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql b/src/main/resources/db/changelog/223-hs-office-relation-rbac.sql similarity index 54% rename from src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql rename to src/main/resources/db/changelog/223-hs-office-relation-rbac.sql index 126664a4..6a7d55a1 100644 --- a/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac.sql @@ -1,97 +1,97 @@ --liquibase formatted sql -- ============================================================================ ---changeset hs-office-relationship-rbac-OBJECT:1 endDelimiter:--// +--changeset hs-office-relation-rbac-OBJECT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRelatedRbacObject('hs_office_relationship'); +call generateRelatedRbacObject('hs_office_relation'); --// -- ============================================================================ ---changeset hs-office-relationship-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +--changeset hs-office-relation-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacRoleDescriptors('hsOfficeRelationship', 'hs_office_relationship'); +call generateRbacRoleDescriptors('hsOfficeRelation', 'hs_office_relation'); --// -- ============================================================================ ---changeset hs-office-relationship-rbac-ROLES-CREATION:1 endDelimiter:--// +--changeset hs-office-relation-rbac-ROLES-CREATION:1 endDelimiter:--// -- ---------------------------------------------------------------------------- /* - Creates and updates the roles and their assignments for relationship entities. + Creates and updates the roles and their assignments for relation entities. */ -create or replace function hsOfficeRelationshipRbacRolesTrigger() +create or replace function hsOfficeRelationRbacRolesTrigger() returns trigger language plpgsql strict as $$ declare - hsOfficeRelationshipTenant RbacRoleDescriptor; - newRelAnchor hs_office_person; - newRelHolder hs_office_person; + hsOfficeRelationTenant RbacRoleDescriptor; + newAnchor hs_office_person; + newHolder hs_office_person; oldContact hs_office_contact; newContact hs_office_contact; begin call enterTriggerForObjectUuid(NEW.uuid); - hsOfficeRelationshipTenant := hsOfficeRelationshipTenant(NEW); + hsOfficeRelationTenant := hsOfficeRelationTenant(NEW); - select * from hs_office_person as p where p.uuid = NEW.relAnchorUuid into newRelAnchor; - select * from hs_office_person as p where p.uuid = NEW.relHolderUuid into newRelHolder; + 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; if TG_OP = 'INSERT' then perform createRoleWithGrants( - hsOfficeRelationshipOwner(NEW), + hsOfficeRelationOwner(NEW), permissions => array['DELETE'], incomingSuperRoles => array[ globalAdmin(), - hsOfficePersonAdmin(newRelAnchor)] + hsOfficePersonAdmin(newAnchor)] ); perform createRoleWithGrants( - hsOfficeRelationshipAdmin(NEW), + hsOfficeRelationAdmin(NEW), permissions => array['UPDATE'], - incomingSuperRoles => array[hsOfficeRelationshipOwner(NEW)] + incomingSuperRoles => array[hsOfficeRelationOwner(NEW)] ); -- the tenant role for those related users who can view the data perform createRoleWithGrants( - hsOfficeRelationshipTenant, + hsOfficeRelationTenant, permissions => array['SELECT'], incomingSuperRoles => array[ - hsOfficeRelationshipAdmin(NEW), - hsOfficePersonAdmin(newRelAnchor), - hsOfficePersonAdmin(newRelHolder), + hsOfficeRelationAdmin(NEW), + hsOfficePersonAdmin(newAnchor), + hsOfficePersonAdmin(newHolder), hsOfficeContactAdmin(newContact)], outgoingSubRoles => array[ - hsOfficePersonTenant(newRelAnchor), - hsOfficePersonTenant(newRelHolder), + hsOfficePersonTenant(newAnchor), + hsOfficePersonTenant(newHolder), hsOfficeContactTenant(newContact)] ); -- anchor and holder admin roles need each others tenant role - -- to be able to see the joined relationship + -- to be able to see the joined relation -- TODO: this can probably be avoided through agent+guest roles - call grantRoleToRole(hsOfficePersonTenant(newRelAnchor), hsOfficePersonAdmin(newRelHolder)); - call grantRoleToRole(hsOfficePersonTenant(newRelHolder), hsOfficePersonAdmin(newRelAnchor)); - call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newRelHolder), hsOfficeContactAdmin(newContact)); + call grantRoleToRole(hsOfficePersonTenant(newAnchor), hsOfficePersonAdmin(newHolder)); + call grantRoleToRole(hsOfficePersonTenant(newHolder), hsOfficePersonAdmin(newAnchor)); + call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newHolder), hsOfficeContactAdmin(newContact)); elsif TG_OP = 'UPDATE' then if OLD.contactUuid <> NEW.contactUuid then -- nothing but the contact can be updated, - -- in other cases, a new relationship needs to be created and the old 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( hsOfficeRelationshipTenant, hsOfficeContactAdmin(oldContact) ); - call grantRoleToRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(newContact) ); + call revokeRoleFromRole( hsOfficeRelationTenant, hsOfficeContactAdmin(oldContact) ); + call grantRoleToRole( hsOfficeRelationTenant, hsOfficeContactAdmin(newContact) ); - call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationshipTenant ); - call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationshipTenant ); + call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationTenant ); + call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationTenant ); end if; else raise exception 'invalid usage of TRIGGER'; @@ -104,39 +104,39 @@ end; $$; /* An AFTER INSERT TRIGGER which creates the role structure for a new customer. */ -create trigger createRbacRolesForHsOfficeRelationship_Trigger +create trigger createRbacRolesForHsOfficeRelation_Trigger after insert - on hs_office_relationship + on hs_office_relation for each row -execute procedure hsOfficeRelationshipRbacRolesTrigger(); +execute procedure hsOfficeRelationRbacRolesTrigger(); /* An AFTER UPDATE TRIGGER which updates the role structure of a customer. */ -create trigger updateRbacRolesForHsOfficeRelationship_Trigger +create trigger updateRbacRolesForHsOfficeRelation_Trigger after update - on hs_office_relationship + on hs_office_relation for each row -execute procedure hsOfficeRelationshipRbacRolesTrigger(); +execute procedure hsOfficeRelationRbacRolesTrigger(); --// -- ============================================================================ ---changeset hs-office-relationship-rbac-IDENTITY-VIEW:1 endDelimiter:--// +--changeset hs-office-relation-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('hs_office_relationship', $idName$ - (select idName from hs_office_person_iv p where p.uuid = target.relAnchorUuid) - || '-with-' || target.relType || '-' || - (select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid) +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$); --// -- ============================================================================ ---changeset hs-office-relationship-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +--changeset hs-office-relation-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacRestrictedView('hs_office_relationship', - '(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)', +call generateRbacRestrictedView('hs_office_relation', + '(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)', $updates$ contactUuid = new.contactUuid $updates$); @@ -146,10 +146,10 @@ call generateRbacRestrictedView('hs_office_relationship', -- ============================================================================ ---changeset hs-office-relationship-rbac-NEW-RELATHIONSHIP:1 endDelimiter:--// +--changeset hs-office-relation-rbac-NEW-RELATHIONSHIP:1 endDelimiter:--// -- ---------------------------------------------------------------------------- /* - Creates a global permission for new-relationship and assigns it to the hostsharing admins role. + Creates a global permission for new-relation and assigns it to the hostsharing admins role. */ do language plpgsql $$ declare @@ -157,11 +157,11 @@ do language plpgsql $$ globalObjectUuid uuid; globalAdminRoleUuid uuid ; begin - call defineContext('granting global new-relationship permission to global admin role', null, null, null); + 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-relationship']); + addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-relation']); call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions); end; $$; @@ -169,24 +169,24 @@ $$; /** Used by the trigger to prevent the add-customer to current user respectively assumed roles. */ -create or replace function addHsOfficeRelationshipNotAllowedForCurrentSubjects() +create or replace function addHsOfficeRelationNotAllowedForCurrentSubjects() returns trigger language PLPGSQL as $$ begin - raise exception '[403] new-relationship not permitted for %', + 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_relationship_insert_trigger +create trigger hs_office_relation_insert_trigger before insert - on hs_office_relationship + on hs_office_relation for each row - -- TODO.spec: who is allowed to create new relationships + -- TODO.spec: who is allowed to create new relations when ( not hasAssumedRole() ) -execute procedure addHsOfficeRelationshipNotAllowedForCurrentSubjects(); +execute procedure addHsOfficeRelationNotAllowedForCurrentSubjects(); --// diff --git a/src/main/resources/db/changelog/228-hs-office-relationship-test-data.sql b/src/main/resources/db/changelog/228-hs-office-relation-test-data.sql similarity index 57% rename from src/main/resources/db/changelog/228-hs-office-relationship-test-data.sql rename to src/main/resources/db/changelog/228-hs-office-relation-test-data.sql index 39c15ac2..8ad39359 100644 --- a/src/main/resources/db/changelog/228-hs-office-relationship-test-data.sql +++ b/src/main/resources/db/changelog/228-hs-office-relation-test-data.sql @@ -2,15 +2,15 @@ -- ============================================================================ ---changeset hs-office-relationship-TEST-DATA-GENERATOR:1 endDelimiter:--// +--changeset hs-office-relation-TEST-DATA-GENERATOR:1 endDelimiter:--// -- ---------------------------------------------------------------------------- /* - Creates a single relationship test record. + Creates a single relation test record. */ -create or replace procedure createHsOfficeRelationshipTestData( +create or replace procedure createHsOfficeRelationTestData( holderPersonName varchar, - relationshipType HsOfficeRelationshipType, + relationType HsOfficeRelationType, anchorPersonTradeName varchar, contactLabel varchar, mark varchar default null) @@ -24,7 +24,7 @@ declare begin idName := cleanIdentifier( anchorPersonTradeName || '-' || holderPersonName); - currentTask := 'creating relationship test-data ' || idName; + 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); @@ -45,20 +45,20 @@ begin raise exception 'contact "%" not found', contactLabel; end if; - raise notice 'creating test relationship: %', idName; + raise notice 'creating test relation: %', idName; raise notice '- using anchor person (%): %', anchorPerson.uuid, anchorPerson; raise notice '- using holder person (%): %', holderPerson.uuid, holderPerson; raise notice '- using contact (%): %', contact.uuid, contact; insert - into hs_office_relationship (uuid, relanchoruuid, relholderuuid, reltype, relmark, contactUuid) - values (uuid_generate_v4(), anchorPerson.uuid, holderPerson.uuid, relationshipType, mark, contact.uuid); + into hs_office_relation (uuid, anchoruuid, holderuuid, type, mark, contactUuid) + values (uuid_generate_v4(), anchorPerson.uuid, holderPerson.uuid, relationType, mark, contact.uuid); end; $$; --// /* - Creates a range of test relationship for mass data generation. + Creates a range of test relation for mass data generation. */ -create or replace procedure createHsOfficeRelationshipTestData( +create or replace procedure createHsOfficeRelationTestData( startCount integer, -- count of auto generated rows before the run endCount integer -- count of auto generated rows after the run ) @@ -72,7 +72,7 @@ begin select p.* from hs_office_person p where tradeName = intToVarChar(t, 4) into person; select c.* from hs_office_contact c where c.label = intToVarChar(t, 4) || '#' || t into contact; - call createHsOfficeRelationshipTestData(person.uuid, contact.uuid, 'REPRESENTATIVE'); + call createHsOfficeRelationTestData(person.uuid, contact.uuid, 'REPRESENTATIVE'); commit; end loop; end; $$; @@ -80,25 +80,25 @@ end; $$; -- ============================================================================ ---changeset hs-office-relationship-TEST-DATA-GENERATION:1 –context=dev,tc endDelimiter:--// +--changeset hs-office-relation-TEST-DATA-GENERATION:1 –context=dev,tc endDelimiter:--// -- ---------------------------------------------------------------------------- do language plpgsql $$ begin - call createHsOfficeRelationshipTestData('First GmbH', 'PARTNER', 'Hostsharing eG', 'first contact'); - call createHsOfficeRelationshipTestData('Firby', 'REPRESENTATIVE', 'First GmbH', 'first contact'); + call createHsOfficeRelationTestData('First GmbH', 'PARTNER', 'Hostsharing eG', 'first contact'); + call createHsOfficeRelationTestData('Firby', 'REPRESENTATIVE', 'First GmbH', 'first contact'); - call createHsOfficeRelationshipTestData('Second e.K.', 'PARTNER', 'Hostsharing eG', 'second contact'); - call createHsOfficeRelationshipTestData('Smith', 'REPRESENTATIVE', 'Second e.K.', 'second contact'); + call createHsOfficeRelationTestData('Second e.K.', 'PARTNER', 'Hostsharing eG', 'second contact'); + call createHsOfficeRelationTestData('Smith', 'REPRESENTATIVE', 'Second e.K.', 'second contact'); - call createHsOfficeRelationshipTestData('Third OHG', 'PARTNER', 'Hostsharing eG', 'third contact'); - call createHsOfficeRelationshipTestData('Tucker', 'REPRESENTATIVE', 'Third OHG', 'third contact'); + call createHsOfficeRelationTestData('Third OHG', 'PARTNER', 'Hostsharing eG', 'third contact'); + call createHsOfficeRelationTestData('Tucker', 'REPRESENTATIVE', 'Third OHG', 'third contact'); - call createHsOfficeRelationshipTestData('Fourth eG', 'PARTNER', 'Hostsharing eG', 'fourth contact'); - call createHsOfficeRelationshipTestData('Fouler', 'REPRESENTATIVE', 'Third OHG', 'third contact'); + call createHsOfficeRelationTestData('Fourth eG', 'PARTNER', 'Hostsharing eG', 'fourth contact'); + call createHsOfficeRelationTestData('Fouler', 'REPRESENTATIVE', 'Third OHG', 'third contact'); - call createHsOfficeRelationshipTestData('Smith', 'PARTNER', 'Hostsharing eG', 'sixth contact'); - call createHsOfficeRelationshipTestData('Smith', 'SUBSCRIBER', 'Third OHG', 'third contact', 'members-announce'); + call createHsOfficeRelationTestData('Smith', 'PARTNER', 'Hostsharing eG', 'sixth contact'); + call createHsOfficeRelationTestData('Smith', 'SUBSCRIBER', 'Third OHG', 'third contact', 'members-announce'); end; $$; --// diff --git a/src/main/resources/db/changelog/230-hs-office-partner.sql b/src/main/resources/db/changelog/230-hs-office-partner.sql index d1db4400..73a02fa1 100644 --- a/src/main/resources/db/changelog/230-hs-office-partner.sql +++ b/src/main/resources/db/changelog/230-hs-office-partner.sql @@ -33,9 +33,9 @@ create table hs_office_partner ( uuid uuid unique references RbacObject (uuid) initially deferred, partnerNumber numeric(5) unique not null, - partnerRoleUuid uuid not null references hs_office_relationship(uuid), -- TODO: delete in after delete trigger - personUuid uuid not null references hs_office_person(uuid), -- TODO: remove, replaced by partnerRoleUuid - contactUuid uuid not null references hs_office_contact(uuid), -- TODO: remove, replaced by partnerRoleUuid + 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 detailsUuid uuid not null references hs_office_partner_details(uuid) -- deleted in after delete trigger ); --// diff --git a/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql b/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql index d16048fd..c2882dbb 100644 --- a/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql +++ b/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql @@ -27,8 +27,8 @@ create or replace function hsOfficePartnerRbacRolesTrigger() language plpgsql strict as $$ declare - oldPartnerRole hs_office_relationship; - newPartnerRole hs_office_relationship; + oldPartnerRel hs_office_relation; + newPartnerRel hs_office_relation; oldPerson hs_office_person; newPerson hs_office_person; @@ -38,7 +38,7 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - select * from hs_office_relationship as r where r.uuid = NEW.partnerroleuuid into newPartnerRole; + 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; @@ -58,7 +58,7 @@ begin incomingSuperRoles => array[ hsOfficePartnerOwner(NEW)], outgoingSubRoles => array[ - hsOfficeRelationshipTenant(newPartnerRole), + hsOfficeRelationTenant(newPartnerRel), hsOfficePersonTenant(newPerson), hsOfficeContactTenant(newContact)] ); @@ -67,7 +67,7 @@ begin hsOfficePartnerAgent(NEW), incomingSuperRoles => array[ hsOfficePartnerAdmin(NEW), - hsOfficeRelationshipAdmin(newPartnerRole), + hsOfficeRelationAdmin(newPartnerRel), hsOfficePersonAdmin(newPerson), hsOfficeContactAdmin(newContact)] ); @@ -77,7 +77,7 @@ begin incomingSuperRoles => array[ hsOfficePartnerAgent(NEW)], outgoingSubRoles => array[ - hsOfficeRelationshipTenant(newPartnerRole), + hsOfficeRelationTenant(newPartnerRel), hsOfficePersonGuest(newPerson), hsOfficeContactGuest(newContact)] ); @@ -118,17 +118,17 @@ begin elsif TG_OP = 'UPDATE' then - if OLD.partnerRoleUuid <> NEW.partnerRoleUuid then - select * from hs_office_relationship as r where r.uuid = OLD.partnerRoleUuid into oldPartnerRole; + if OLD.partnerRelUuid <> NEW.partnerRelUuid then + select * from hs_office_relation as r where r.uuid = OLD.partnerRelUuid into oldPartnerRel; - call revokeRoleFromRole(hsOfficeRelationshipTenant(oldPartnerRole), hsOfficePartnerAdmin(OLD)); - call grantRoleToRole(hsOfficeRelationshipTenant(newPartnerRole), hsOfficePartnerAdmin(NEW)); + call revokeRoleFromRole(hsOfficeRelationTenant(oldPartnerRel), hsOfficePartnerAdmin(OLD)); + call grantRoleToRole(hsOfficeRelationTenant(newPartnerRel), hsOfficePartnerAdmin(NEW)); - call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficeRelationshipAdmin(oldPartnerRole)); - call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficeRelationshipAdmin(newPartnerRole)); + call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficeRelationAdmin(oldPartnerRel)); + call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficeRelationAdmin(newPartnerRel)); - call revokeRoleFromRole(hsOfficeRelationshipGuest(oldPartnerRole), hsOfficePartnerTenant(OLD)); - call grantRoleToRole(hsOfficeRelationshipGuest(newPartnerRole), hsOfficePartnerTenant(NEW)); + call revokeRoleFromRole(hsOfficeRelationGuest(oldPartnerRel), hsOfficePartnerTenant(OLD)); + call grantRoleToRole(hsOfficeRelationGuest(newPartnerRel), hsOfficePartnerTenant(NEW)); end if; if OLD.personUuid <> NEW.personUuid then @@ -202,7 +202,7 @@ call generateRbacIdentityViewFromProjection('hs_office_partner', $idName$ call generateRbacRestrictedView('hs_office_partner', '(select idName from hs_office_person_iv p where p.uuid = target.personUuid)', $updates$ - partnerRoleUuid = new.partnerRoleUuid, + partnerRelUuid = new.partnerRelUuid, personUuid = new.personUuid, contactUuid = new.contactUuid $updates$); diff --git a/src/main/resources/db/changelog/238-hs-office-partner-test-data.sql b/src/main/resources/db/changelog/238-hs-office-partner-test-data.sql index 146f2f1d..765803a8 100644 --- a/src/main/resources/db/changelog/238-hs-office-partner-test-data.sql +++ b/src/main/resources/db/changelog/238-hs-office-partner-test-data.sql @@ -18,7 +18,7 @@ declare currentTask varchar; idName varchar; mandantPerson hs_office_person; - partnerRole hs_office_relationship; + partnerRel hs_office_relation; relatedPerson hs_office_person; relatedContact hs_office_contact; relatedDetailsUuid uuid; @@ -42,16 +42,16 @@ begin where c.label = contactLabel into relatedContact; - select r.* from hs_office_relationship r - where r.reltype = 'PARTNER' - and r.relanchoruuid = mandantPerson.uuid and r.relholderuuid = relatedPerson.uuid - into partnerRole; - if partnerRole is null then - raise exception 'partnerRole "%"-"%" not found', mandantPerson.tradename, partnerPersonName; + select r.* from hs_office_relation r + where r.type = 'PARTNER' + and r.anchoruuid = mandantPerson.uuid and r.holderuuid = relatedPerson.uuid + into partnerRel; + if partnerRel is null then + raise exception 'partnerRel "%"-"%" not found', mandantPerson.tradename, partnerPersonName; end if; raise notice 'creating test partner: %', idName; - raise notice '- using partnerRole (%): %', partnerRole.uuid, partnerRole; + raise notice '- using partnerRel (%): %', partnerRel.uuid, partnerRel; raise notice '- using person (%): %', relatedPerson.uuid, relatedPerson; raise notice '- using contact (%): %', relatedContact.uuid, relatedContact; @@ -68,8 +68,8 @@ begin end if; insert - into hs_office_partner (uuid, partnerNumber, partnerRoleUuid, personuuid, contactuuid, detailsUuid) - values (uuid_generate_v4(), partnerNumber, partnerRole.uuid, relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid); + into hs_office_partner (uuid, partnerNumber, partnerRelUuid, personuuid, contactuuid, detailsUuid) + values (uuid_generate_v4(), partnerNumber, partnerRel.uuid, relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid); end; $$; --// diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 2b8417c3..5934c9a4 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -68,11 +68,11 @@ databaseChangeLog: - include: file: db/changelog/218-hs-office-person-test-data.sql - include: - file: db/changelog/220-hs-office-relationship.sql + file: db/changelog/220-hs-office-relation.sql - include: - file: db/changelog/223-hs-office-relationship-rbac.sql + file: db/changelog/223-hs-office-relation-rbac.sql - include: - file: db/changelog/228-hs-office-relationship-test-data.sql + file: db/changelog/228-hs-office-relation-test-data.sql - include: file: db/changelog/230-hs-office-partner.sql - include: diff --git a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java index 013b2309..fa49e102 100644 --- a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java +++ b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java @@ -41,7 +41,7 @@ public class ArchitectureTest { "..hs.office.migration", "..hs.office.partner", "..hs.office.person", - "..hs.office.relationship", + "..hs.office.relation", "..hs.office.sepamandate", "..errors", "..mapper", @@ -148,7 +148,7 @@ public class ArchitectureTest { public static final ArchRule hsOfficeContactPackageRule = classes() .that().resideInAPackage("..hs.office.contact..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.contact..", "..hs.office.relationship..", + .resideInAnyPackage("..hs.office.contact..", "..hs.office.relation..", "..hs.office.partner..", "..hs.office.debitor..", "..hs.office.membership..", @@ -159,7 +159,7 @@ public class ArchitectureTest { public static final ArchRule hsOfficePersonPackageRule = classes() .that().resideInAPackage("..hs.office.person..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.person..", "..hs.office.relationship..", + .resideInAnyPackage("..hs.office.person..", "..hs.office.relation..", "..hs.office.partner..", "..hs.office.debitor..", "..hs.office.membership..", @@ -167,10 +167,10 @@ public class ArchitectureTest { @ArchTest @SuppressWarnings("unused") - public static final ArchRule hsOfficeRelationshipPackageRule = classes() - .that().resideInAPackage("..hs.office.relationship..") + public static final ArchRule hsOfficeRelationPackageRule = classes() + .that().resideInAPackage("..hs.office.relation..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.relationship..", + .resideInAnyPackage("..hs.office.relation..", "..hs.office.partner..", "..hs.office.migration.."); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java index 0c86dc66..c78ad519 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java @@ -18,8 +18,8 @@ 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.relationship.HsOfficeRelationshipEntity; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType; import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.test.JpaAttempt; @@ -127,7 +127,7 @@ public class ImportOfficeData extends ContextBasedTest { new String[]{"partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"}, SUBSCRIBER_ROLES); - static int relationshipId = 2000000; + static int relationId = 2000000; @Value("${spring.datasource.url}") private String jdbcUrl; @@ -144,7 +144,7 @@ public class ImportOfficeData extends ContextBasedTest { private static Map debitors = new WriteOnceMap<>(); private static Map memberships = new WriteOnceMap<>(); - private static Map relationships = new WriteOnceMap<>(); + private static Map relations = new WriteOnceMap<>(); private static Map sepaMandates = new WriteOnceMap<>(); private static Map bankAccounts = new WriteOnceMap<>(); private static Map coopShares = new WriteOnceMap<>(); @@ -220,17 +220,17 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(1021) - void buildDebitorRelationships() { + void buildDebitorRelations() { debitors.forEach( (id, debitor) -> { - final var debitorRel = HsOfficeRelationshipEntity.builder() - .relType(HsOfficeRelationshipType.DEBITOR) - .relAnchor(debitor.getPartner().getPartnerRole().getRelHolder()) - .relHolder(debitor.getPartner().getPartnerRole().getRelHolder()) // just 1 debitor/partner in legacy hsadmin + final var debitorRel = HsOfficeRelationEntity.builder() + .type(HsOfficeRelationType.DEBITOR) + .anchor(debitor.getPartner().getPartnerRel().getHolder()) + .holder(debitor.getPartner().getPartnerRel().getHolder()) // just 1 debitor/partner in legacy hsadmin .contact(debitor.getBillingContact()) .build(); - if (debitorRel.getRelAnchor() != null && debitorRel.getRelHolder() != null && + if (debitorRel.getAnchor() != null && debitorRel.getHolder() != null && debitorRel.getContact() != null ) { - relationships.put(relationshipId++, debitorRel); + relations.put(relationId++, debitorRel); } }); } @@ -288,28 +288,28 @@ public class ImportOfficeData extends ContextBasedTest { 22=Membership(M-1102200, ?? Test PS, D-1102200, [2021-04-01,), NONE) } """); - assertThat(toFormattedString(relationships)).isEqualToIgnoringWhitespace(""" + assertThat(toFormattedString(relations)).isEqualToIgnoringWhitespace(""" { - 2000000=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000001=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000002=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000003=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='null null, null'), - 2000004=rel(relAnchor='NP Mellies, Michael', relType='OPERATIONS', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000005=rel(relAnchor='NP Mellies, Michael', relType='REPRESENTATIVE', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000006=rel(relAnchor='LP JM GmbH', relType='EX_PARTNER', relHolder='LP JM e.K.', contact='JM e.K.'), - 2000007=rel(relAnchor='LP JM GmbH', relType='OPERATIONS', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000008=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000009=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='operations-announce', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000010=rel(relAnchor='LP JM GmbH', relType='REPRESENTATIVE', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000011=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='members-announce', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000012=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='customers-announce', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000013=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), - 2000014=rel(relAnchor='?? Test PS', relType='OPERATIONS', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000015=rel(relAnchor='?? Test PS', relType='REPRESENTATIVE', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000016=rel(relAnchor='NP Mellies, Michael', relType='SUBSCRIBER', relMark='operations-announce', relHolder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '), - 2000017=rel(relAnchor='NP Mellies, Michael', relType='DEBITOR', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000018=rel(relAnchor='LP JM GmbH', relType='DEBITOR', relHolder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), - 2000019=rel(relAnchor='?? Test PS', relType='DEBITOR', relHolder='?? Test PS', contact='Petra Schmidt , Test PS') + 2000000=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000001=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000002=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000003=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'), + 2000004=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000005=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000006=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), + 2000007=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000008=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000009=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000010=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000011=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000012=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000013=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), + 2000014=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000015=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000016=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '), + 2000017=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000018=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), + 2000019=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS') } """); } @@ -419,20 +419,20 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(2009) - void removeEmptyRelationships() { + void removeEmptyRelations() { assumeThatWeAreImportingControlledTestData(); // avoid a error when persisting the deliberetely invalid partner entry #99 final var idsToRemove = new HashSet(); - relationships.forEach( (id, r) -> { + relations.forEach( (id, r) -> { // such a record if (r.getContact() == null || r.getContact().getLabel() == null || - r.getRelHolder() == null | r.getRelHolder().getPersonType() == null ) { + r.getHolder() == null | r.getHolder().getPersonType() == null ) { idsToRemove.add(id); } }); assertThat(idsToRemove.size()).isEqualTo(1); // only from partner #99 (partner+contractual roles) - idsToRemove.forEach(id -> relationships.remove(id)); + idsToRemove.forEach(id -> relations.remove(id)); } @Test @@ -495,7 +495,7 @@ public class ImportOfficeData extends ContextBasedTest { jpaAttempt.transacted(() -> { context(rbacSuperuser); - relationships.forEach(this::persist); + relations.forEach(this::persist); }).assertSuccessful(); jpaAttempt.transacted(() -> { @@ -572,7 +572,7 @@ public class ImportOfficeData extends ContextBasedTest { em.createNativeQuery("delete from hs_office_bankaccount where true").executeUpdate(); em.createNativeQuery("delete from hs_office_partner where true").executeUpdate(); em.createNativeQuery("delete from hs_office_partner_details where true").executeUpdate(); - em.createNativeQuery("delete from hs_office_relationship where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_relation where true").executeUpdate(); em.createNativeQuery("delete from hs_office_contact where true").executeUpdate(); em.createNativeQuery("delete from hs_office_person where true").executeUpdate(); }).assertSuccessful(); @@ -676,18 +676,18 @@ public class ImportOfficeData extends ContextBasedTest { .forEach(rec -> { final var person = HsOfficePersonEntity.builder().build(); - final var partnerRelationship = HsOfficeRelationshipEntity.builder() - .relHolder(person) - .relType(HsOfficeRelationshipType.PARTNER) - .relAnchor(mandant) + final var partnerRelation = HsOfficeRelationEntity.builder() + .holder(person) + .type(HsOfficeRelationType.PARTNER) + .anchor(mandant) .contact(null) // is set during contacts import depending on assigned roles .build(); - relationships.put(relationshipId++, partnerRelationship); + relations.put(relationId++, partnerRelation); final var partner = HsOfficePartnerEntity.builder() .partnerNumber(rec.getInteger("member_id")) .details(HsOfficePartnerDetailsEntity.builder().build()) - .partnerRole(partnerRelationship) + .partnerRel(partnerRelation) .contact(null) // is set during contacts import depending on assigned roles .person(person) .build(); @@ -845,7 +845,7 @@ public class ImportOfficeData extends ContextBasedTest { final var debitor = debitors.get(bpId); final var partnerPerson = partner.getPerson(); - if (containsPartnerRole(rec)) { + if (containsPartnerRel(rec)) { initPerson(partner.getPerson(), rec); } @@ -859,46 +859,46 @@ public class ImportOfficeData extends ContextBasedTest { final var contact = HsOfficeContactEntity.builder().build(); initContact(contact, rec); - if (containsPartnerRole(rec)) { + if (containsPartnerRel(rec)) { assertThat(partner.getContact()).isNull(); partner.setContact(contact); - partner.getPartnerRole().setContact(contact); + partner.getPartnerRel().setContact(contact); } if (containsRole(rec, "billing")) { assertThat(debitor.getBillingContact()).isNull(); debitor.setBillingContact(contact); } if (containsRole(rec, "operation")) { - addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.OPERATIONS); + addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.OPERATIONS); } if (containsRole(rec, "contractual")) { - addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.REPRESENTATIVE); + addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.REPRESENTATIVE); } if (containsRole(rec, "ex-partner")) { - addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.EX_PARTNER); + addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.EX_PARTNER); } if (containsRole(rec, "vip-contact")) { - addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.VIP_CONTACT); + addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.VIP_CONTACT); } for (String subscriberRole: SUBSCRIBER_ROLES) { if (containsRole(rec, subscriberRole)) { - addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.SUBSCRIBER) - .setRelMark(subscriberRole.split(":")[1]) + addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.SUBSCRIBER) + .setMark(subscriberRole.split(":")[1]) ; } } verifyContainsOnlyKnownRoles(rec.getString("roles")); }); - optionallyAddMissingContractualRelationships(); + optionallyAddMissingContractualRelations(); } - private static void optionallyAddMissingContractualRelationships() { + private static void optionallyAddMissingContractualRelations() { final var contractualMissing = new HashSet(); partners.forEach( (id, partner) -> { final var partnerPerson = partner.getPerson(); - if (relationships.values().stream() - .filter(rel -> rel.getRelAnchor() == partnerPerson && rel.getRelType() == HsOfficeRelationshipType.REPRESENTATIVE) + if (relations.values().stream() + .filter(rel -> rel.getAnchor() == partnerPerson && rel.getType() == HsOfficeRelationType.REPRESENTATIVE) .findFirst().isEmpty()) { contractualMissing.add(partner.getPartnerNumber()); } @@ -909,22 +909,22 @@ public class ImportOfficeData extends ContextBasedTest { return ("," + roles + ",").contains("," + role + ","); } - private static boolean containsPartnerRole(final Record rec) { + private static boolean containsPartnerRel(final Record rec) { return containsRole(rec, "partner"); } - private static HsOfficeRelationshipEntity addRelationship( + private static HsOfficeRelationEntity addRelation( final HsOfficePersonEntity partnerPerson, final HsOfficePersonEntity contactPerson, final HsOfficeContactEntity contact, - final HsOfficeRelationshipType representative) { - final var rel = HsOfficeRelationshipEntity.builder() - .relAnchor(partnerPerson) - .relHolder(contactPerson) + final HsOfficeRelationType representative) { + final var rel = HsOfficeRelationEntity.builder() + .anchor(partnerPerson) + .holder(contactPerson) .contact(contact) - .relType(representative) + .type(representative) .build(); - relationships.put(relationshipId++, rel); + relations.put(relationId++, rel); return rel; } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java index 33a312c4..9e712a2d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java @@ -7,9 +7,9 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.test.Accepts; import net.hostsharing.test.JpaAttempt; @@ -41,7 +41,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu HsOfficePartnerRepository partnerRepo; @Autowired - HsOfficeRelationshipRepository relationshipRepository; + HsOfficeRelationRepository relationRepository; @Autowired HsOfficePersonRepository personRepo; @@ -102,9 +102,9 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu .body(""" { "partnerNumber": "20002", - "partnerRole": { - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "partnerRel": { + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" }, "personUuid": "%s", @@ -155,9 +155,9 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu .body(""" { "partnerNumber": "20003", - "partnerRole": { - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "partnerRel": { + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" }, "personUuid": "%s", @@ -193,9 +193,9 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu .body(""" { "partnerNumber": "20004", - "partnerRole": { - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "partnerRel": { + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" }, "personUuid": "%s", @@ -413,7 +413,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu // then the given partner is gone assertThat(partnerRepo.findByUuid(givenPartner.getUuid())).isEmpty(); - assertThat(relationshipRepository.findByUuid(givenPartner.getPartnerRole().getUuid())).isEmpty(); + assertThat(relationRepository.findByUuid(givenPartner.getPartnerRel().getUuid())).isEmpty(); } @Test @@ -465,15 +465,15 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0); - final var partnerRole = new HsOfficeRelationshipEntity(); - partnerRole.setRelType(HsOfficeRelationshipType.PARTNER); - partnerRole.setRelAnchor(givenMandantPerson); - partnerRole.setRelHolder(givenPerson); - partnerRole.setContact(givenContact); - em.persist(partnerRole); + final var partnerRel = new HsOfficeRelationEntity(); + partnerRel.setType(HsOfficeRelationType.PARTNER); + partnerRel.setAnchor(givenMandantPerson); + partnerRel.setHolder(givenPerson); + partnerRel.setContact(givenContact); + em.persist(partnerRel); final var newPartner = HsOfficePartnerEntity.builder() - .partnerRole(partnerRole) + .partnerRel(partnerRel) .partnerNumber(partnerNumber) .person(givenPerson) .contact(givenContact) @@ -492,6 +492,6 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu cleanupAllNew(HsOfficePartnerEntity.class); // TODO: should not be necessary anymore, once it's deleted via after delete trigger - cleanupAllNew(HsOfficeRelationshipEntity.class); + cleanupAllNew(HsOfficeRelationEntity.class); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java index ed04d899..e86cbc94 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java @@ -3,8 +3,8 @@ package net.hostsharing.hsadminng.hs.office.partner; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository; import net.hostsharing.hsadminng.mapper.Mapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -54,7 +54,7 @@ class HsOfficePartnerControllerRestTest { HsOfficePartnerRepository partnerRepo; @MockBean - HsOfficeRelationshipRepository relationshipRepo; + HsOfficeRelationRepository relationRepo; @MockBean EntityManager em; @@ -100,9 +100,9 @@ class HsOfficePartnerControllerRestTest { .content(""" { "partnerNumber": "20002", - "partnerRole": { - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "partnerRel": { + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" }, "personUuid": "%s", @@ -137,9 +137,9 @@ class HsOfficePartnerControllerRestTest { .content(""" { "partnerNumber": "20002", - "partnerRole": { - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "partnerRel": { + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" }, "personUuid": "%s", @@ -175,11 +175,11 @@ class HsOfficePartnerControllerRestTest { when(partnerRepo.findByUuid(givenPartnerUuid)).thenReturn(Optional.of(partnerMock)); when(partnerRepo.deleteByUuid(givenPartnerUuid)).thenReturn(0); - final UUID givenRelationshipUuid = UUID.randomUUID(); - when(partnerMock.getPartnerRole()).thenReturn(HsOfficeRelationshipEntity.builder() - .uuid(givenRelationshipUuid) + final UUID givenRelationUuid = UUID.randomUUID(); + when(partnerMock.getPartnerRel()).thenReturn(HsOfficeRelationEntity.builder() + .uuid(givenRelationUuid) .build()); - when(relationshipRepo.deleteByUuid(givenRelationshipUuid)).thenReturn(0); + when(relationRepo.deleteByUuid(givenRelationUuid)).thenReturn(0); // when mockMvc.perform(MockMvcRequestBuilders @@ -193,18 +193,18 @@ class HsOfficePartnerControllerRestTest { } @Test - void respondBadRequest_ifRelationshipCannotBeDeleted() throws Exception { + void respondBadRequest_ifRelationCannotBeDeleted() throws Exception { // given final UUID givenPartnerUuid = UUID.randomUUID(); when(partnerRepo.findByUuid(givenPartnerUuid)).thenReturn(Optional.of(partnerMock)); when(partnerRepo.deleteByUuid(givenPartnerUuid)).thenReturn(1); - when(relationshipRepo.deleteByUuid(any())).thenReturn(0); + when(relationRepo.deleteByUuid(any())).thenReturn(0); - final UUID givenRelationshipUuid = UUID.randomUUID(); - when(partnerMock.getPartnerRole()).thenReturn(HsOfficeRelationshipEntity.builder() - .uuid(givenRelationshipUuid) + final UUID givenRelationUuid = UUID.randomUUID(); + when(partnerMock.getPartnerRel()).thenReturn(HsOfficeRelationEntity.builder() + .uuid(givenRelationUuid) .build()); - when(relationshipRepo.deleteByUuid(givenRelationshipUuid)).thenReturn(0); + when(relationRepo.deleteByUuid(givenRelationUuid)).thenReturn(0); // when mockMvc.perform(MockMvcRequestBuilders diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index 94d06a77..75eaac3e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -3,9 +3,9 @@ package net.hostsharing.hsadminng.hs.office.partner; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository; -import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; +import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository; +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.rbacrole.RawRbacRoleRepository; @@ -43,7 +43,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean HsOfficePartnerRepository partnerRepo; @Autowired - HsOfficeRelationshipRepository relationshipRepo; + HsOfficeRelationRepository relationRepo; @Autowired HsOfficePersonRepository personRepo; @@ -80,19 +80,19 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean final var givenPartnerPerson = personRepo.findPersonByOptionalNameLike("First GmbH").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("first contact").get(0); - final var partnerRole = HsOfficeRelationshipEntity.builder() - .relHolder(givenPartnerPerson) - .relType(HsOfficeRelationshipType.PARTNER) - .relAnchor(givenMandantorPerson) + final var partnerRel = HsOfficeRelationEntity.builder() + .holder(givenPartnerPerson) + .type(HsOfficeRelationType.PARTNER) + .anchor(givenMandantorPerson) .contact(givenContact) .build(); - relationshipRepo.save(partnerRole); + relationRepo.save(partnerRel); // when final var result = attempt(em, () -> { final var newPartner = HsOfficePartnerEntity.builder() .partnerNumber(20031) - .partnerRole(partnerRole) + .partnerRel(partnerRel) .person(givenPartnerPerson) .contact(givenContact) .details(HsOfficePartnerDetailsEntity.builder() @@ -125,17 +125,17 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0); final var givenMandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0); - final var newRelationship = HsOfficeRelationshipEntity.builder() - .relHolder(givenPartnerPerson) - .relType(HsOfficeRelationshipType.PARTNER) - .relAnchor(givenMandantPerson) + final var newRelation = HsOfficeRelationEntity.builder() + .holder(givenPartnerPerson) + .type(HsOfficeRelationType.PARTNER) + .anchor(givenMandantPerson) .contact(givenContact) .build(); - relationshipRepo.save(newRelationship); + relationRepo.save(newRelation); final var newPartner = HsOfficePartnerEntity.builder() .partnerNumber(20032) - .partnerRole(newRelationship) + .partnerRel(newRelation) .person(givenPartnerPerson) .contact(givenContact) .details(HsOfficePartnerDetailsEntity.builder().build()) @@ -146,9 +146,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean // then assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialRoleNames, - "hs_office_relationship#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.admin", - "hs_office_relationship#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.owner", - "hs_office_relationship#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.tenant", + "hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.admin", + "hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.owner", + "hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.tenant", "hs_office_partner#20032:ErbenBesslerMelBessler-fourthcontact.admin", "hs_office_partner#20032:ErbenBesslerMelBessler-fourthcontact.agent", "hs_office_partner#20032:ErbenBesslerMelBessler-fourthcontact.owner", @@ -160,25 +160,25 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean .map(s -> s.replace("hs_office_", "")) .containsExactlyInAnyOrder(distinct(fromFormatted( initialGrantNames, - // relationship - TODO: check and cleanup + // relation - TODO: check and cleanup "{ grant role person#HostsharingeG.tenant to role person#EBess.admin by system and assume }", "{ grant role person#EBess.tenant to role person#HostsharingeG.admin by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role partner#20032:EBess-4th.admin by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role partner#20032:EBess-4th.tenant by system and assume }", - "{ grant role partner#20032:EBess-4th.agent to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to role global#global.admin by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role contact#4th.admin by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role person#EBess.admin by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to role person#HostsharingeG.admin by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role person#HostsharingeG.admin by system and assume }", - "{ grant perm UPDATE on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }", - "{ grant perm DELETE on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }", - "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.admin to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }", - "{ grant perm SELECT on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }", - "{ grant role contact#4th.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }", - "{ grant role person#EBess.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }", - "{ grant role person#HostsharingeG.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role partner#20032:EBess-4th.admin by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role partner#20032:EBess-4th.tenant by system and assume }", + "{ grant role partner#20032:EBess-4th.agent to role relation#HostsharingeG-with-PARTNER-EBess.admin by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.owner to role global#global.admin by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role contact#4th.admin by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role person#EBess.admin by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.owner to role person#HostsharingeG.admin by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role person#HostsharingeG.admin by system and assume }", + "{ grant perm UPDATE on relation#HostsharingeG-with-PARTNER-EBess to role relation#HostsharingeG-with-PARTNER-EBess.admin by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role relation#HostsharingeG-with-PARTNER-EBess.admin by system and assume }", + "{ grant perm DELETE on relation#HostsharingeG-with-PARTNER-EBess to role relation#HostsharingeG-with-PARTNER-EBess.owner by system and assume }", + "{ grant role relation#HostsharingeG-with-PARTNER-EBess.admin to role relation#HostsharingeG-with-PARTNER-EBess.owner by system and assume }", + "{ grant perm SELECT on relation#HostsharingeG-with-PARTNER-EBess to role relation#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }", + "{ grant role contact#4th.tenant to role relation#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }", + "{ grant role person#EBess.tenant to role relation#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }", + "{ grant role person#HostsharingeG.tenant to role relation#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }", // owner "{ grant perm DELETE on partner#20032:EBess-4th to role partner#20032:EBess-4th.owner by system and assume }", @@ -426,15 +426,15 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - // TODO: should deleting a partner automatically delete the PARTNER relationship? (same for debitor) + // TODO: should deleting a partner automatically delete the PARTNER relation? (same for debitor) // TODO: why did the test cleanup check does not notice this, if missing? return partnerRepo.deleteByUuid(givenPartner.getUuid()) + - relationshipRepo.deleteByUuid(givenPartner.getPartnerRole().getUuid()); + relationRepo.deleteByUuid(givenPartner.getPartnerRel().getUuid()); }); // then result.assertSuccessful(); - assertThat(result.returnedValue()).isEqualTo(2); // partner+relationship + assertThat(result.returnedValue()).isEqualTo(2); // partner+relation assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(initialRoleNames); assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(initialGrantNames); } @@ -466,17 +466,17 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean final var givenPartnerPerson = personRepo.findPersonByOptionalNameLike(person).get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0); - final var partnerRole = HsOfficeRelationshipEntity.builder() - .relHolder(givenPartnerPerson) - .relType(HsOfficeRelationshipType.PARTNER) - .relAnchor(givenMandantorPerson) + final var partnerRel = HsOfficeRelationEntity.builder() + .holder(givenPartnerPerson) + .type(HsOfficeRelationType.PARTNER) + .anchor(givenMandantorPerson) .contact(givenContact) .build(); - relationshipRepo.save(partnerRole); + relationRepo.save(partnerRel); final var newPartner = HsOfficePartnerEntity.builder() .partnerNumber(partnerNumber) - .partnerRole(partnerRole) + .partnerRel(partnerRel) .person(givenPartnerPerson) .contact(givenContact) .details(HsOfficePartnerDetailsEntity.builder().build()) @@ -502,7 +502,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean void cleanup() { cleanupAllNew(HsOfficePartnerDetailsEntity.class); // TODO: should not be necessary cleanupAllNew(HsOfficePartnerEntity.class); - cleanupAllNew(HsOfficeRelationshipEntity.class); + cleanupAllNew(HsOfficeRelationEntity.class); } private String[] distinct(final String[] strings) { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java similarity index 63% rename from src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipControllerAcceptanceTest.java rename to src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java index 8f9e9147..fd978e1d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java @@ -1,4 +1,4 @@ -package net.hostsharing.hsadminng.hs.office.relationship; +package net.hostsharing.hsadminng.hs.office.relation; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -7,7 +7,7 @@ import net.hostsharing.test.Accepts; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; -import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationshipTypeResource; +import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationTypeResource; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; import net.hostsharing.test.JpaAttempt; import org.json.JSONException; @@ -31,7 +31,7 @@ import static org.hamcrest.Matchers.startsWith; classes = { HsadminNgApplication.class, JpaAttempt.class } ) @Transactional -class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithCleanup { +class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithCleanup { public static final UUID GIVEN_NON_EXISTING_HOLDER_PERSON_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); @LocalServerPort @@ -44,7 +44,7 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC Context contextMock; @Autowired - HsOfficeRelationshipRepository relationshipRepo; + HsOfficeRelationRepository relationRepo; @Autowired HsOfficePersonRepository personRepo; @@ -56,11 +56,11 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC JpaAttempt jpaAttempt; @Nested - @Accepts({ "Relationship:F(Find)" }) - class ListRelationships { + @Accepts({ "Relation:F(Find)" }) + class ListRelations { @Test - void globalAdmin_withoutAssumedRoles_canViewAllRelationshipsOfGivenPersonAndType_ifNoCriteriaGiven() throws JSONException { + void globalAdmin_withoutAssumedRoles_canViewAllRelationsOfGivenPersonAndType_ifNoCriteriaGiven() throws JSONException { // given context.define("superuser-alex@hostsharing.net"); @@ -71,45 +71,45 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC .header("current-user", "superuser-alex@hostsharing.net") .port(port) .when() - .get("http://localhost/api/hs/office/relationships?personUuid=%s&relationshipType=%s" - .formatted(givenPerson.getUuid(), HsOfficeRelationshipTypeResource.PARTNER)) + .get("http://localhost/api/hs/office/relations?personUuid=%s&relationType=%s" + .formatted(givenPerson.getUuid(), HsOfficeRelationTypeResource.PARTNER)) .then().log().all().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" [ { - "relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, - "relHolder": { "personType": "LEGAL_PERSON", "tradeName": "First GmbH" }, - "relType": "PARTNER", - "relMark": null, + "anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, + "holder": { "personType": "LEGAL_PERSON", "tradeName": "First GmbH" }, + "type": "PARTNER", + "mark": null, "contact": { "label": "first contact" } }, { - "relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, - "relHolder": { "personType": "INCORPORATED_FIRM", "tradeName": "Fourth eG" }, - "relType": "PARTNER", + "anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, + "holder": { "personType": "INCORPORATED_FIRM", "tradeName": "Fourth eG" }, + "type": "PARTNER", "contact": { "label": "fourth contact" } }, { - "relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, - "relHolder": { "personType": "LEGAL_PERSON", "tradeName": "Second e.K.", "givenName": "Peter", "familyName": "Smith" }, - "relType": "PARTNER", - "relMark": null, + "anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, + "holder": { "personType": "LEGAL_PERSON", "tradeName": "Second e.K.", "givenName": "Peter", "familyName": "Smith" }, + "type": "PARTNER", + "mark": null, "contact": { "label": "second contact" } }, { - "relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, - "relHolder": { "personType": "NATURAL_PERSON", "givenName": "Peter", "familyName": "Smith" }, - "relType": "PARTNER", - "relMark": null, + "anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, + "holder": { "personType": "NATURAL_PERSON", "givenName": "Peter", "familyName": "Smith" }, + "type": "PARTNER", + "mark": null, "contact": { "label": "sixth contact" } }, { - "relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, - "relHolder": { "personType": "INCORPORATED_FIRM", "tradeName": "Third OHG" }, - "relType": "PARTNER", - "relMark": null, + "anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" }, + "holder": { "personType": "INCORPORATED_FIRM", "tradeName": "Third OHG" }, + "type": "PARTNER", + "mark": null, "contact": { "label": "third contact" } } ] @@ -119,11 +119,11 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC } @Nested - @Accepts({ "Relationship:C(Create)" }) - class AddRelationship { + @Accepts({ "Relation:C(Create)" }) + class AddRelation { @Test - void globalAdmin_withoutAssumedRole_canAddRelationship() { + void globalAdmin_withoutAssumedRole_canAddRelation() { context.define("superuser-alex@hostsharing.net"); final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0); @@ -136,38 +136,38 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC .contentType(ContentType.JSON) .body(""" { - "relType": "%s", - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "type": "%s", + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" } """.formatted( - HsOfficeRelationshipTypeResource.ACCOUNTING, + HsOfficeRelationTypeResource.DEBITOR, givenAnchorPerson.getUuid(), givenHolderPerson.getUuid(), givenContact.getUuid())) .port(port) .when() - .post("http://localhost/api/hs/office/relationships") + .post("http://localhost/api/hs/office/relations") .then().log().all().assertThat() .statusCode(201) .contentType(ContentType.JSON) .body("uuid", isUuidValid()) - .body("relType", is("ACCOUNTING")) - .body("relAnchor.tradeName", is("Third OHG")) - .body("relHolder.givenName", is("Paul")) + .body("type", is("DEBITOR")) + .body("anchor.tradeName", is("Third OHG")) + .body("holder.givenName", is("Paul")) .body("contact.label", is("second contact")) .header("Location", startsWith("http://localhost")) .extract().header("Location"); // @formatter:on - // finally, the new relationship can be accessed under the generated UUID - final var newUserUuid = toCleanup(HsOfficeRelationshipEntity.class, UUID.fromString( + // finally, the new relation can be accessed under the generated UUID + final var newUserUuid = toCleanup(HsOfficeRelationEntity.class, UUID.fromString( location.substring(location.lastIndexOf('/') + 1))); assertThat(newUserUuid).isNotNull(); } @Test - void globalAdmin_canNotAddRelationship_ifAnchorPersonDoesNotExist() { + void globalAdmin_canNotAddRelation_ifAnchorPersonDoesNotExist() { context.define("superuser-alex@hostsharing.net"); final var givenAnchorPersonUuid = GIVEN_NON_EXISTING_HOLDER_PERSON_UUID; @@ -180,27 +180,27 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC .contentType(ContentType.JSON) .body(""" { - "relType": "%s", - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "type": "%s", + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" } """.formatted( - HsOfficeRelationshipTypeResource.ACCOUNTING, + HsOfficeRelationTypeResource.DEBITOR, givenAnchorPersonUuid, givenHolderPerson.getUuid(), givenContact.getUuid())) .port(port) .when() - .post("http://localhost/api/hs/office/relationships") + .post("http://localhost/api/hs/office/relations") .then().log().all().assertThat() .statusCode(404) - .body("message", is("cannot find relAnchorUuid " + GIVEN_NON_EXISTING_HOLDER_PERSON_UUID)); + .body("message", is("cannot find anchorUuid " + GIVEN_NON_EXISTING_HOLDER_PERSON_UUID)); // @formatter:on } @Test - void globalAdmin_canNotAddRelationship_ifHolderPersonDoesNotExist() { + void globalAdmin_canNotAddRelation_ifHolderPersonDoesNotExist() { context.define("superuser-alex@hostsharing.net"); final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0); @@ -212,27 +212,27 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC .contentType(ContentType.JSON) .body(""" { - "relType": "%s", - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "type": "%s", + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" } """.formatted( - HsOfficeRelationshipTypeResource.ACCOUNTING, + HsOfficeRelationTypeResource.DEBITOR, givenAnchorPerson.getUuid(), GIVEN_NON_EXISTING_HOLDER_PERSON_UUID, givenContact.getUuid())) .port(port) .when() - .post("http://localhost/api/hs/office/relationships") + .post("http://localhost/api/hs/office/relations") .then().log().all().assertThat() .statusCode(404) - .body("message", is("cannot find relHolderUuid " + GIVEN_NON_EXISTING_HOLDER_PERSON_UUID)); + .body("message", is("cannot find holderUuid " + GIVEN_NON_EXISTING_HOLDER_PERSON_UUID)); // @formatter:on } @Test - void globalAdmin_canNotAddRelationship_ifContactDoesNotExist() { + void globalAdmin_canNotAddRelation_ifContactDoesNotExist() { context.define("superuser-alex@hostsharing.net"); final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0); @@ -245,19 +245,19 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC .contentType(ContentType.JSON) .body(""" { - "relType": "%s", - "relAnchorUuid": "%s", - "relHolderUuid": "%s", + "type": "%s", + "anchorUuid": "%s", + "holderUuid": "%s", "contactUuid": "%s" } """.formatted( - HsOfficeRelationshipTypeResource.ACCOUNTING, + HsOfficeRelationTypeResource.DEBITOR, givenAnchorPerson.getUuid(), givenHolderPerson.getUuid(), givenContactUuid)) .port(port) .when() - .post("http://localhost/api/hs/office/relationships") + .post("http://localhost/api/hs/office/relations") .then().log().all().assertThat() .statusCode(404) .body("message", is("cannot find contactUuid 00000000-0000-0000-0000-000000000000")); @@ -266,97 +266,97 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC } @Nested - @Accepts({ "Relationship:R(Read)" }) - class GetRelationship { + @Accepts({ "Relation:R(Read)" }) + class GetRelation { @Test - void globalAdmin_withoutAssumedRole_canGetArbitraryRelationship() { + void globalAdmin_withoutAssumedRole_canGetArbitraryRelation() { context.define("superuser-alex@hostsharing.net"); - final UUID givenRelationshipUuid = findRelationship("First", "Firby").getUuid(); + final UUID givenRelationUuid = findRelation("First", "Firby").getUuid(); RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") .port(port) .when() - .get("http://localhost/api/hs/office/relationships/" + givenRelationshipUuid) + .get("http://localhost/api/hs/office/relations/" + givenRelationUuid) .then().log().body().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" { - "relAnchor": { "tradeName": "First GmbH" }, - "relHolder": { "familyName": "Firby" }, + "anchor": { "tradeName": "First GmbH" }, + "holder": { "familyName": "Firby" }, "contact": { "label": "first contact" } } """)); // @formatter:on } @Test - @Accepts({ "Relationship:X(Access Control)" }) - void normalUser_canNotGetUnrelatedRelationship() { + @Accepts({ "Relation:X(Access Control)" }) + void normalUser_canNotGetUnrelatedRelation() { context.define("superuser-alex@hostsharing.net"); - final UUID givenRelationshipUuid = findRelationship("First", "Firby").getUuid(); + final UUID givenRelationUuid = findRelation("First", "Firby").getUuid(); RestAssured // @formatter:off .given() .header("current-user", "selfregistered-user-drew@hostsharing.org") .port(port) .when() - .get("http://localhost/api/hs/office/relationships/" + givenRelationshipUuid) + .get("http://localhost/api/hs/office/relations/" + givenRelationUuid) .then().log().body().assertThat() .statusCode(404); // @formatter:on } @Test - @Accepts({ "Relationship:X(Access Control)" }) - void contactAdminUser_canGetRelatedRelationship() { + @Accepts({ "Relation:X(Access Control)" }) + void contactAdminUser_canGetRelatedRelation() { context.define("superuser-alex@hostsharing.net"); - final var givenRelationship = findRelationship("First", "Firby"); - assertThat(givenRelationship.getContact().getLabel()).isEqualTo("first contact"); + final var givenRelation = findRelation("First", "Firby"); + assertThat(givenRelation.getContact().getLabel()).isEqualTo("first contact"); RestAssured // @formatter:off .given() .header("current-user", "contact-admin@firstcontact.example.com") .port(port) .when() - .get("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid()) + .get("http://localhost/api/hs/office/relations/" + givenRelation.getUuid()) .then().log().body().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" { - "relAnchor": { "tradeName": "First GmbH" }, - "relHolder": { "familyName": "Firby" }, + "anchor": { "tradeName": "First GmbH" }, + "holder": { "familyName": "Firby" }, "contact": { "label": "first contact" } } """)); // @formatter:on } } - private HsOfficeRelationshipEntity findRelationship( + private HsOfficeRelationEntity findRelation( final String anchorPersonName, final String holderPersoneName) { final var anchorPersonUuid = personRepo.findPersonByOptionalNameLike(anchorPersonName).get(0).getUuid(); final var holderPersonUuid = personRepo.findPersonByOptionalNameLike(holderPersoneName).get(0).getUuid(); - final var givenRelationship = relationshipRepo - .findRelationshipRelatedToPersonUuid(anchorPersonUuid) + final var givenRelation = relationRepo + .findRelationRelatedToPersonUuid(anchorPersonUuid) .stream() - .filter(r -> r.getRelHolder().getUuid().equals(holderPersonUuid)) + .filter(r -> r.getHolder().getUuid().equals(holderPersonUuid)) .findFirst().orElseThrow(); - return givenRelationship; + return givenRelation; } @Nested - @Accepts({ "Relationship:U(Update)" }) - class PatchRelationship { + @Accepts({ "Relation:U(Update)" }) + class PatchRelation { @Test - void globalAdmin_withoutAssumedRole_canPatchContactOfArbitraryRelationship() { + void globalAdmin_withoutAssumedRole_canPatchContactOfArbitraryRelation() { context.define("superuser-alex@hostsharing.net"); - final var givenRelationship = givenSomeTemporaryRelationshipBessler(); - assertThat(givenRelationship.getContact().getLabel()).isEqualTo("seventh contact"); + final var givenRelation = givenSomeTemporaryRelationBessler(); + assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact"); final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); final var location = RestAssured // @formatter:off @@ -370,109 +370,109 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC """.formatted(givenContact.getUuid())) .port(port) .when() - .patch("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid()) + .patch("http://localhost/api/hs/office/relations/" + givenRelation.getUuid()) .then().log().all().assertThat() .statusCode(200) .contentType(ContentType.JSON) .body("uuid", isUuidValid()) - .body("relType", is("REPRESENTATIVE")) - .body("relAnchor.tradeName", is("Erben Bessler")) - .body("relHolder.familyName", is("Winkler")) + .body("type", is("REPRESENTATIVE")) + .body("anchor.tradeName", is("Erben Bessler")) + .body("holder.familyName", is("Winkler")) .body("contact.label", is("fourth contact")); // @formatter:on - // finally, the relationship is actually updated + // finally, the relation is actually updated context.define("superuser-alex@hostsharing.net"); - assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isPresent().get() + assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isPresent().get() .matches(rel -> { - assertThat(rel.getRelAnchor().getTradeName()).contains("Bessler"); - assertThat(rel.getRelHolder().getFamilyName()).contains("Winkler"); + assertThat(rel.getAnchor().getTradeName()).contains("Bessler"); + assertThat(rel.getHolder().getFamilyName()).contains("Winkler"); assertThat(rel.getContact().getLabel()).isEqualTo("fourth contact"); - assertThat(rel.getRelType()).isEqualTo(HsOfficeRelationshipType.REPRESENTATIVE); + assertThat(rel.getType()).isEqualTo(HsOfficeRelationType.REPRESENTATIVE); return true; }); } } @Nested - @Accepts({ "Relationship:D(Delete)" }) - class DeleteRelationship { + @Accepts({ "Relation:D(Delete)" }) + class DeleteRelation { @Test - void globalAdmin_withoutAssumedRole_canDeleteArbitraryRelationship() { + void globalAdmin_withoutAssumedRole_canDeleteArbitraryRelation() { context.define("superuser-alex@hostsharing.net"); - final var givenRelationship = givenSomeTemporaryRelationshipBessler(); + final var givenRelation = givenSomeTemporaryRelationBessler(); RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") .port(port) .when() - .delete("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid()) + .delete("http://localhost/api/hs/office/relations/" + givenRelation.getUuid()) .then().log().body().assertThat() .statusCode(204); // @formatter:on - // then the given relationship is gone - assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isEmpty(); + // then the given relation is gone + assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isEmpty(); } @Test - @Accepts({ "Relationship:X(Access Control)" }) - void contactAdminUser_canNotDeleteRelatedRelationship() { + @Accepts({ "Relation:X(Access Control)" }) + void contactAdminUser_canNotDeleteRelatedRelation() { context.define("superuser-alex@hostsharing.net"); - final var givenRelationship = givenSomeTemporaryRelationshipBessler(); - assertThat(givenRelationship.getContact().getLabel()).isEqualTo("seventh contact"); + final var givenRelation = givenSomeTemporaryRelationBessler(); + assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact"); RestAssured // @formatter:off .given() .header("current-user", "contact-admin@seventhcontact.example.com") .port(port) .when() - .delete("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid()) + .delete("http://localhost/api/hs/office/relations/" + givenRelation.getUuid()) .then().log().body().assertThat() .statusCode(403); // @formatter:on - // then the given relationship is still there - assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isNotEmpty(); + // then the given relation is still there + assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isNotEmpty(); } @Test - @Accepts({ "Relationship:X(Access Control)" }) - void normalUser_canNotDeleteUnrelatedRelationship() { + @Accepts({ "Relation:X(Access Control)" }) + void normalUser_canNotDeleteUnrelatedRelation() { context.define("superuser-alex@hostsharing.net"); - final var givenRelationship = givenSomeTemporaryRelationshipBessler(); - assertThat(givenRelationship.getContact().getLabel()).isEqualTo("seventh contact"); + final var givenRelation = givenSomeTemporaryRelationBessler(); + assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact"); RestAssured // @formatter:off .given() .header("current-user", "selfregistered-user-drew@hostsharing.org") .port(port) .when() - .delete("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid()) + .delete("http://localhost/api/hs/office/relations/" + givenRelation.getUuid()) .then().log().body().assertThat() .statusCode(404); // @formatter:on - // then the given relationship is still there - assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isNotEmpty(); + // then the given relation is still there + assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isNotEmpty(); } } - private HsOfficeRelationshipEntity givenSomeTemporaryRelationshipBessler() { + private HsOfficeRelationEntity givenSomeTemporaryRelationBessler() { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0); final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Winkler").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("seventh contact").get(0); - final var newRelationship = HsOfficeRelationshipEntity.builder() - .relType(HsOfficeRelationshipType.REPRESENTATIVE) - .relAnchor(givenAnchorPerson) - .relHolder(givenHolderPerson) + final var newRelation = HsOfficeRelationEntity.builder() + .type(HsOfficeRelationType.REPRESENTATIVE) + .anchor(givenAnchorPerson) + .holder(givenHolderPerson) .contact(givenContact) .build(); - assertThat(toCleanup(relationshipRepo.save(newRelationship))).isEqualTo(newRelationship); + assertThat(toCleanup(relationRepo.save(newRelation))).isEqualTo(newRelation); - return newRelationship; + return newRelation; }).assertSuccessful().returnedValue(); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityPatcherUnitTest.java similarity index 67% rename from src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcherUnitTest.java rename to src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityPatcherUnitTest.java index 1c12a629..6aec1b25 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityPatcherUnitTest.java @@ -1,7 +1,7 @@ -package net.hostsharing.hsadminng.hs.office.relationship; +package net.hostsharing.hsadminng.hs.office.relation; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; -import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationshipPatchResource; +import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.test.PatchUnitTestBase; import org.junit.jupiter.api.BeforeEach; @@ -21,12 +21,12 @@ import static org.mockito.Mockito.lenient; @TestInstance(PER_CLASS) @ExtendWith(MockitoExtension.class) -class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase< - HsOfficeRelationshipPatchResource, - HsOfficeRelationshipEntity +class HsOfficeRelationEntityPatcherUnitTest extends PatchUnitTestBase< + HsOfficeRelationPatchResource, + HsOfficeRelationEntity > { - static final UUID INITIAL_RELATIONSHIP_UUID = UUID.randomUUID(); + static final UUID INITIAL_RELATION_UUID = UUID.randomUUID(); static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID(); @Mock @@ -49,24 +49,24 @@ class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase< .build(); @Override - protected HsOfficeRelationshipEntity newInitialEntity() { - final var entity = new HsOfficeRelationshipEntity(); - entity.setUuid(INITIAL_RELATIONSHIP_UUID); - entity.setRelType(HsOfficeRelationshipType.REPRESENTATIVE); - entity.setRelAnchor(givenInitialAnchorPerson); - entity.setRelHolder(givenInitialHolderPerson); + protected HsOfficeRelationEntity newInitialEntity() { + final var entity = new HsOfficeRelationEntity(); + entity.setUuid(INITIAL_RELATION_UUID); + entity.setType(HsOfficeRelationType.REPRESENTATIVE); + entity.setAnchor(givenInitialAnchorPerson); + entity.setHolder(givenInitialHolderPerson); entity.setContact(givenInitialContact); return entity; } @Override - protected HsOfficeRelationshipPatchResource newPatchResource() { - return new HsOfficeRelationshipPatchResource(); + protected HsOfficeRelationPatchResource newPatchResource() { + return new HsOfficeRelationPatchResource(); } @Override - protected HsOfficeRelationshipEntityPatcher createPatcher(final HsOfficeRelationshipEntity relationship) { - return new HsOfficeRelationshipEntityPatcher(em, relationship); + protected HsOfficeRelationEntityPatcher createPatcher(final HsOfficeRelationEntity relation) { + return new HsOfficeRelationEntityPatcher(em, relation); } @Override @@ -74,9 +74,9 @@ class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase< return Stream.of( new JsonNullableProperty<>( "contact", - HsOfficeRelationshipPatchResource::setContactUuid, + HsOfficeRelationPatchResource::setContactUuid, PATCHED_CONTACT_UUID, - HsOfficeRelationshipEntity::setContact, + HsOfficeRelationEntity::setContact, newContact(PATCHED_CONTACT_UUID)) .notNullable() ); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityUnitTest.java new file mode 100644 index 00000000..bf2a7ed3 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntityUnitTest.java @@ -0,0 +1,43 @@ +package net.hostsharing.hsadminng.hs.office.relation; + +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class HsOfficeRelationEntityUnitTest { + + private HsOfficePersonEntity anchor = HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.LEGAL_PERSON) + .tradeName("some trade name") + .build(); + private HsOfficePersonEntity holder = HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.NATURAL_PERSON) + .familyName("Meier") + .givenName("Mellie") + .build(); + + @Test + void toStringReturnsAllProperties() { + final var given = HsOfficeRelationEntity.builder() + .type(HsOfficeRelationType.SUBSCRIBER) + .mark("members-announce") + .anchor(anchor) + .holder(holder) + .build(); + + assertThat(given.toString()).isEqualTo("rel(anchor='LP some trade name', type='SUBSCRIBER', mark='members-announce', holder='NP Meier, Mellie')"); + } + + @Test + void toShortString() { + final var given = HsOfficeRelationEntity.builder() + .type(HsOfficeRelationType.REPRESENTATIVE) + .anchor(anchor) + .holder(holder) + .build(); + + assertThat(given.toShortString()).isEqualTo("rel(anchor='LP some trade name', type='REPRESENTATIVE', holder='NP Meier, Mellie')"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java similarity index 54% rename from src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java rename to src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java index 46d60a40..545e7b03 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java @@ -1,4 +1,4 @@ -package net.hostsharing.hsadminng.hs.office.relationship; +package net.hostsharing.hsadminng.hs.office.relation; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; @@ -29,10 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest @Import( { Context.class, JpaAttempt.class }) -class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWithCleanup { +class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithCleanup { @Autowired - HsOfficeRelationshipRepository relationshipRepo; + HsOfficeRelationRepository relationRepo; @Autowired HsOfficePersonRepository personRepo; @@ -56,33 +56,33 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith HttpServletRequest request; @Nested - class CreateRelationship { + class CreateRelation { @Test - public void testHostsharingAdmin_withoutAssumedRole_canCreateNewRelationship() { + public void testHostsharingAdmin_withoutAssumedRole_canCreateNewRelation() { // given context("superuser-alex@hostsharing.net"); - final var count = relationshipRepo.count(); + final var count = relationRepo.count(); final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Bessler").get(0); final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Anita").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0); // when final var result = attempt(em, () -> { - final var newRelationship = HsOfficeRelationshipEntity.builder() - .relAnchor(givenAnchorPerson) - .relHolder(givenHolderPerson) - .relType(HsOfficeRelationshipType.REPRESENTATIVE) + final var newRelation = HsOfficeRelationEntity.builder() + .anchor(givenAnchorPerson) + .holder(givenHolderPerson) + .type(HsOfficeRelationType.REPRESENTATIVE) .contact(givenContact) .build(); - return toCleanup(relationshipRepo.save(newRelationship)); + return toCleanup(relationRepo.save(newRelation)); }); // then result.assertSuccessful(); - assertThat(result.returnedValue()).isNotNull().extracting(HsOfficeRelationshipEntity::getUuid).isNotNull(); - assertThatRelationshipIsPersisted(result.returnedValue()); - assertThat(relationshipRepo.count()).isEqualTo(count + 1); + assertThat(result.returnedValue()).isNotNull().extracting(HsOfficeRelationEntity::getUuid).isNotNull(); + assertThatRelationIsPersisted(result.returnedValue()); + assertThat(relationRepo.count()).isEqualTo(count + 1); } @Test @@ -97,190 +97,190 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Bessler").get(0); final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Anita").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0); - final var newRelationship = HsOfficeRelationshipEntity.builder() - .relAnchor(givenAnchorPerson) - .relHolder(givenHolderPerson) - .relType(HsOfficeRelationshipType.REPRESENTATIVE) + final var newRelation = HsOfficeRelationEntity.builder() + .anchor(givenAnchorPerson) + .holder(givenHolderPerson) + .type(HsOfficeRelationType.REPRESENTATIVE) .contact(givenContact) .build(); - return toCleanup(relationshipRepo.save(newRelationship)); + return toCleanup(relationRepo.save(newRelation)); }); // then assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialRoleNames, - "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin", - "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner", - "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant")); + "hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin", + "hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner", + "hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant")); assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted( initialGrantNames, - "{ grant perm DELETE on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role global#global.admin by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role hs_office_person#BesslerAnita.admin by system and assume }", + "{ grant perm DELETE on hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }", + "{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role global#global.admin by system and assume }", + "{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role hs_office_person#BesslerAnita.admin by system and assume }", - "{ grant perm UPDATE on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }", + "{ grant perm UPDATE on hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }", + "{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }", - "{ grant perm SELECT on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_contact#fourthcontact.admin by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_person#BesslerAnita.admin by system and assume }", + "{ grant perm SELECT on hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", + "{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_contact#fourthcontact.admin by system and assume }", + "{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_person#BesslerAnita.admin by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }", - "{ grant role hs_office_contact#fourthcontact.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", - "{ grant role hs_office_person#BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", + "{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }", + "{ grant role hs_office_contact#fourthcontact.tenant to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", + "{ grant role hs_office_person#BesslerAnita.tenant to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", null) ); } - private void assertThatRelationshipIsPersisted(final HsOfficeRelationshipEntity saved) { - final var found = relationshipRepo.findByUuid(saved.getUuid()); + private void assertThatRelationIsPersisted(final HsOfficeRelationEntity saved) { + final var found = relationRepo.findByUuid(saved.getUuid()); assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved); } } @Nested - class FindAllRelationships { + class FindAllRelations { @Test - public void globalAdmin_withoutAssumedRole_canViewAllRelationshipsOfArbitraryPerson() { + public void globalAdmin_withoutAssumedRole_canViewAllRelationsOfArbitraryPerson() { // given context("superuser-alex@hostsharing.net"); final var person = personRepo.findPersonByOptionalNameLike("Second e.K.").stream().findFirst().orElseThrow(); // when - final var result = relationshipRepo.findRelationshipRelatedToPersonUuid(person.getUuid()); + final var result = relationRepo.findRelationRelatedToPersonUuid(person.getUuid()); // then - allTheseRelationshipsAreReturned( + allTheseRelationsAreReturned( result, - "rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='LP Second e.K.', contact='second contact')", - "rel(relAnchor='LP Second e.K.', relType='REPRESENTATIVE', relHolder='NP Smith, Peter', contact='second contact')"); + "rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP Second e.K.', contact='second contact')", + "rel(anchor='LP Second e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')"); } @Test - public void normalUser_canViewRelationshipsOfOwnedPersons() { + public void normalUser_canViewRelationsOfOwnedPersons() { // given: context("person-FirstGmbH@example.com"); final var person = personRepo.findPersonByOptionalNameLike("First").stream().findFirst().orElseThrow(); // when: - final var result = relationshipRepo.findRelationshipRelatedToPersonUuid(person.getUuid()); + final var result = relationRepo.findRelationRelatedToPersonUuid(person.getUuid()); // then: - exactlyTheseRelationshipsAreReturned( + exactlyTheseRelationsAreReturned( result, - "rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='LP First GmbH', contact='first contact')", - "rel(relAnchor='LP First GmbH', relType='REPRESENTATIVE', relHolder='NP Firby, Susan', contact='first contact')"); + "rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP First GmbH', contact='first contact')", + "rel(anchor='LP First GmbH', type='REPRESENTATIVE', holder='NP Firby, Susan', contact='first contact')"); } } @Nested - class UpdateRelationship { + class UpdateRelation { @Test - public void hostsharingAdmin_withoutAssumedRole_canUpdateContactOfArbitraryRelationship() { + public void hostsharingAdmin_withoutAssumedRole_canUpdateContactOfArbitraryRelation() { // given context("superuser-alex@hostsharing.net"); - final var givenRelationship = givenSomeTemporaryRelationshipBessler( + final var givenRelation = givenSomeTemporaryRelationBessler( "Anita", "fifth contact"); - assertThatRelationshipIsVisibleForUserWithRole( - givenRelationship, + assertThatRelationIsVisibleForUserWithRole( + givenRelation, "hs_office_person#ErbenBesslerMelBessler.admin"); - assertThatRelationshipActuallyInDatabase(givenRelationship); + assertThatRelationActuallyInDatabase(givenRelation); context("superuser-alex@hostsharing.net"); final var givenContact = contactRepo.findContactByOptionalLabelLike("sixth contact").get(0); // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - givenRelationship.setContact(givenContact); - return toCleanup(relationshipRepo.save(givenRelationship)); + givenRelation.setContact(givenContact); + return toCleanup(relationRepo.save(givenRelation)); }); // then result.assertSuccessful(); assertThat(result.returnedValue().getContact().getLabel()).isEqualTo("sixth contact"); - assertThatRelationshipIsVisibleForUserWithRole( + assertThatRelationIsVisibleForUserWithRole( result.returnedValue(), "global#global.admin"); - assertThatRelationshipIsVisibleForUserWithRole( + assertThatRelationIsVisibleForUserWithRole( result.returnedValue(), "hs_office_contact#sixthcontact.admin"); - assertThatRelationshipIsNotVisibleForUserWithRole( + assertThatRelationIsNotVisibleForUserWithRole( result.returnedValue(), "hs_office_contact#fifthcontact.admin"); - relationshipRepo.deleteByUuid(givenRelationship.getUuid()); + relationRepo.deleteByUuid(givenRelation.getUuid()); } @Test - public void relHolderAdmin_canNotUpdateRelatedRelationship() { + public void holderAdmin_canNotUpdateRelatedRelation() { // given context("superuser-alex@hostsharing.net"); - final var givenRelationship = givenSomeTemporaryRelationshipBessler( + final var givenRelation = givenSomeTemporaryRelationBessler( "Anita", "eighth"); - assertThatRelationshipIsVisibleForUserWithRole( - givenRelationship, + assertThatRelationIsVisibleForUserWithRole( + givenRelation, "hs_office_person#BesslerAnita.admin"); - assertThatRelationshipActuallyInDatabase(givenRelationship); + assertThatRelationActuallyInDatabase(givenRelation); // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net", "hs_office_person#BesslerAnita.admin"); - givenRelationship.setContact(null); - return relationshipRepo.save(givenRelationship); + givenRelation.setContact(null); + return relationRepo.save(givenRelation); }); // then result.assertExceptionWithRootCauseMessage(JpaSystemException.class, - "[403] Subject ", " is not allowed to update hs_office_relationship uuid"); + "[403] Subject ", " is not allowed to update hs_office_relation uuid"); } @Test - public void contactAdmin_canNotUpdateRelatedRelationship() { + public void contactAdmin_canNotUpdateRelatedRelation() { // given context("superuser-alex@hostsharing.net"); - final var givenRelationship = givenSomeTemporaryRelationshipBessler( + final var givenRelation = givenSomeTemporaryRelationBessler( "Anita", "ninth"); - assertThatRelationshipIsVisibleForUserWithRole( - givenRelationship, + assertThatRelationIsVisibleForUserWithRole( + givenRelation, "hs_office_contact#ninthcontact.admin"); - assertThatRelationshipActuallyInDatabase(givenRelationship); + assertThatRelationActuallyInDatabase(givenRelation); // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net", "hs_office_contact#ninthcontact.admin"); - givenRelationship.setContact(null); // TODO - return relationshipRepo.save(givenRelationship); + givenRelation.setContact(null); // TODO + return relationRepo.save(givenRelation); }); // then result.assertExceptionWithRootCauseMessage(JpaSystemException.class, - "[403] Subject ", " is not allowed to update hs_office_relationship uuid"); + "[403] Subject ", " is not allowed to update hs_office_relation uuid"); } - private void assertThatRelationshipActuallyInDatabase(final HsOfficeRelationshipEntity saved) { - final var found = relationshipRepo.findByUuid(saved.getUuid()); + private void assertThatRelationActuallyInDatabase(final HsOfficeRelationEntity saved) { + final var found = relationRepo.findByUuid(saved.getUuid()); assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved); } - private void assertThatRelationshipIsVisibleForUserWithRole( - final HsOfficeRelationshipEntity entity, + private void assertThatRelationIsVisibleForUserWithRole( + final HsOfficeRelationEntity entity, final String assumedRoles) { jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net", assumedRoles); - assertThatRelationshipActuallyInDatabase(entity); + assertThatRelationActuallyInDatabase(entity); }).assertSuccessful(); } - private void assertThatRelationshipIsNotVisibleForUserWithRole( - final HsOfficeRelationshipEntity entity, + private void assertThatRelationIsNotVisibleForUserWithRole( + final HsOfficeRelationEntity entity, final String assumedRoles) { jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net", assumedRoles); - final var found = relationshipRepo.findByUuid(entity.getUuid()); + final var found = relationRepo.findByUuid(entity.getUuid()); assertThat(found).isEmpty(); }).assertSuccessful(); } @@ -290,63 +290,63 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith class DeleteByUuid { @Test - public void globalAdmin_withoutAssumedRole_canDeleteAnyRelationship() { + public void globalAdmin_withoutAssumedRole_canDeleteAnyRelation() { // given context("superuser-alex@hostsharing.net", null); - final var givenRelationship = givenSomeTemporaryRelationshipBessler( + final var givenRelation = givenSomeTemporaryRelationBessler( "Anita", "tenth"); // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - relationshipRepo.deleteByUuid(givenRelationship.getUuid()); + relationRepo.deleteByUuid(givenRelation.getUuid()); }); // then result.assertSuccessful(); assertThat(jpaAttempt.transacted(() -> { context("superuser-fran@hostsharing.net", null); - return relationshipRepo.findByUuid(givenRelationship.getUuid()); + return relationRepo.findByUuid(givenRelation.getUuid()); }).assertSuccessful().returnedValue()).isEmpty(); } @Test - public void contactUser_canViewButNotDeleteTheirRelatedRelationship() { + public void contactUser_canViewButNotDeleteTheirRelatedRelation() { // given context("superuser-alex@hostsharing.net", null); - final var givenRelationship = givenSomeTemporaryRelationshipBessler( + final var givenRelation = givenSomeTemporaryRelationBessler( "Anita", "eleventh"); // when final var result = jpaAttempt.transacted(() -> { context("contact-admin@eleventhcontact.example.com"); - assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isPresent(); - relationshipRepo.deleteByUuid(givenRelationship.getUuid()); + assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isPresent(); + relationRepo.deleteByUuid(givenRelation.getUuid()); }); // then result.assertExceptionWithRootCauseMessage( JpaSystemException.class, - "[403] Subject ", " not allowed to delete hs_office_relationship"); + "[403] Subject ", " not allowed to delete hs_office_relation"); assertThat(jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - return relationshipRepo.findByUuid(givenRelationship.getUuid()); + return relationRepo.findByUuid(givenRelation.getUuid()); }).assertSuccessful().returnedValue()).isPresent(); // still there } @Test - public void deletingARelationshipAlsoDeletesRelatedRolesAndGrants() { + public void deletingARelationAlsoDeletesRelatedRolesAndGrants() { // given context("superuser-alex@hostsharing.net"); final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll())); final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll())); - final var givenRelationship = givenSomeTemporaryRelationshipBessler( + final var givenRelation = givenSomeTemporaryRelationBessler( "Anita", "twelfth"); // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - return relationshipRepo.deleteByUuid(givenRelationship.getUuid()); + return relationRepo.deleteByUuid(givenRelation.getUuid()); }); // then @@ -363,7 +363,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith final var query = em.createNativeQuery(""" select currentTask, targetTable, targetOp from tx_journal_v - where targettable = 'hs_office_relationship'; + where targettable = 'hs_office_relation'; """); // when @@ -371,40 +371,40 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith // then assertThat(customerLogEntries).map(Arrays::toString).contains( - "[creating relationship test-data HostsharingeG-FirstGmbH, hs_office_relationship, INSERT]", - "[creating relationship test-data FirstGmbH-Firby, hs_office_relationship, INSERT]"); + "[creating relation test-data HostsharingeG-FirstGmbH, hs_office_relation, INSERT]", + "[creating relation test-data FirstGmbH-Firby, hs_office_relation, INSERT]"); } - private HsOfficeRelationshipEntity givenSomeTemporaryRelationshipBessler(final String holderPerson, final String contact) { + private HsOfficeRelationEntity givenSomeTemporaryRelationBessler(final String holderPerson, final String contact) { return jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0); final var givenHolderPerson = personRepo.findPersonByOptionalNameLike(holderPerson).get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0); - final var newRelationship = HsOfficeRelationshipEntity.builder() - .relType(HsOfficeRelationshipType.REPRESENTATIVE) - .relAnchor(givenAnchorPerson) - .relHolder(givenHolderPerson) + final var newRelation = HsOfficeRelationEntity.builder() + .type(HsOfficeRelationType.REPRESENTATIVE) + .anchor(givenAnchorPerson) + .holder(givenHolderPerson) .contact(givenContact) .build(); - return toCleanup(relationshipRepo.save(newRelationship)); + return toCleanup(relationRepo.save(newRelation)); }).assertSuccessful().returnedValue(); } - void exactlyTheseRelationshipsAreReturned( - final List actualResult, - final String... relationshipNames) { + void exactlyTheseRelationsAreReturned( + final List actualResult, + final String... relationNames) { assertThat(actualResult) - .extracting(HsOfficeRelationshipEntity::toString) - .containsExactlyInAnyOrder(relationshipNames); + .extracting(HsOfficeRelationEntity::toString) + .containsExactlyInAnyOrder(relationNames); } - void allTheseRelationshipsAreReturned( - final List actualResult, - final String... relationshipNames) { + void allTheseRelationsAreReturned( + final List actualResult, + final String... relationNames) { assertThat(actualResult) - .extracting(HsOfficeRelationshipEntity::toString) - .contains(relationshipNames); + .extracting(HsOfficeRelationEntity::toString) + .contains(relationNames); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityUnitTest.java deleted file mode 100644 index 59433fa2..00000000 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityUnitTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.hostsharing.hsadminng.hs.office.relationship; - -import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; -import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -class HsOfficeRelationshipEntityUnitTest { - - private HsOfficePersonEntity anchor = HsOfficePersonEntity.builder() - .personType(HsOfficePersonType.LEGAL_PERSON) - .tradeName("some trade name") - .build(); - private HsOfficePersonEntity holder = HsOfficePersonEntity.builder() - .personType(HsOfficePersonType.NATURAL_PERSON) - .familyName("Meier") - .givenName("Mellie") - .build(); - - @Test - void toStringReturnsAllProperties() { - final var given = HsOfficeRelationshipEntity.builder() - .relType(HsOfficeRelationshipType.SUBSCRIBER) - .relMark("members-announce") - .relAnchor(anchor) - .relHolder(holder) - .build(); - - assertThat(given.toString()).isEqualTo("rel(relAnchor='LP some trade name', relType='SUBSCRIBER', relMark='members-announce', relHolder='NP Meier, Mellie')"); - } - - @Test - void toShortString() { - final var given = HsOfficeRelationshipEntity.builder() - .relType(HsOfficeRelationshipType.REPRESENTATIVE) - .relAnchor(anchor) - .relHolder(holder) - .build(); - - assertThat(given.toShortString()).isEqualTo("rel(relAnchor='LP some trade name', relType='REPRESENTATIVE', relHolder='NP Meier, Mellie')"); - } -} diff --git a/tools/generate b/tools/generate deleted file mode 100755 index 93aa5c7c..00000000 --- a/tools/generate +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -sourceLower=partner -targetLower=relationship - -sourceStudly=Partner -targetStudly=Relationship - -## for source in `find src -iname ""*$sourceLower*"" -type f \( -iname \*.yaml -o -iname \*.sql -o -iname \*.java \)`; do -for source in `find src -iname ""*$sourceLower*"" -type f \( -iname \*.yaml \)`; do - target=`echo $source | sed -e "s/$sourceStudly/$targetStudly/g" -e "s/$sourceLower/$targetLower/g"` - echo "Generating $target from $source:" - - mkdir -p `dirname $target` - - sed -e 's/hs-office-partner/hs-office-relationship/g' \ - -e 's/hs_office_partner/hs_office_relationship/g' \ - -e 's/HsOfficePartner/HsOfficeRelationship/g' \ - -e 's/hsOfficePartner/hsOfficeRelationship/g' \ - -e 's/partner/relationship/g' \ - \ - -e 's/addPartner/addRelationship/g' \ - -e 's/listPartners/listRelationships/g' \ - -e 's/getPartnerByUuid/getRelationshipByUuid/g' \ - -e 's/patchPartner/patchRelationship/g' \ - -e 's/person/relHolder/g' \ - -e 's/registrationOffice/relType/g' \ - <$source >$target - -done - -exit - -cat >>src/main/resources/db/changelog/db.changelog-master.yaml < Date: Thu, 21 Mar 2024 10:00:20 +0100 Subject: [PATCH 3/8] copy improved generators from branch remove-direct-partner-person-and-contact --- .../office/debitor/HsOfficeDebitorEntity.java | 2 +- .../partner/HsOfficePartnerDetailsEntity.java | 2 +- .../office/partner/HsOfficePartnerEntity.java | 2 +- .../rbac/rbacdef/InsertTriggerGenerator.java | 155 ++++++++++++++---- .../rbacdef/RbacIdentityViewGenerator.java | 2 +- .../rbacdef/RbacRestrictedViewGenerator.java | 6 +- .../hsadminng/rbac/rbacdef/RbacView.java | 76 +++++---- .../rbacdef/RbacViewPostgresGenerator.java | 7 +- .../RolesGrantsAndPermissionsGenerator.java | 96 +++++++++-- .../hsadminng/rbac/rbacdef/StringWriter.java | 18 +- .../rbacgrant/RbacGrantsDiagramService.java | 46 ++++-- .../hsadminng/test/dom/TestDomainEntity.java | 2 +- .../hsadminng/test/pac/TestPackageEntity.java | 2 +- .../db/changelog/113-test-customer-rbac.md | 2 +- .../db/changelog/113-test-customer-rbac.sql | 20 ++- .../db/changelog/123-test-package-rbac.md | 2 +- .../db/changelog/123-test-package-rbac.sql | 58 ++++--- .../db/changelog/133-test-domain-rbac.md | 2 +- .../db/changelog/133-test-domain-rbac.sql | 58 ++++--- 19 files changed, 405 insertions(+), 153 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index 66f82f95..b010ed9b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -131,7 +131,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { "vatBusiness", "vatReverseCharge", "defaultPrefix" /* TODO: do we want that updatable? */) - .createPermission(custom("new-debitor")).grantedTo("global", ADMIN) + .createPermission(INSERT).grantedTo("global", ADMIN) .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, fetchedBySql(""" diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java index 435357fe..31ff56ab 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java @@ -84,7 +84,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable { "birthName", "birthday", "dateOfDeath") - .createPermission(custom("new-partner-details")).grantedTo("global", ADMIN) + .createPermission(INSERT).grantedTo("global", ADMIN) .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, fetchedBySql(""" diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java index 8e35e9b0..5aaa5318 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java @@ -90,7 +90,7 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid { "partnerRelUuid", "personUuid", "contactUuid") - .createPermission(custom("new-partner")).grantedTo("global", ADMIN) + .createPermission(INSERT).grantedTo("global", ADMIN) .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, fetchedBySql("SELECT * FROM hs_office_relation AS r WHERE r.uuid = ${ref}.partnerRelUuid"), diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index 5303c27e..88d07efa 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -4,6 +4,7 @@ import java.util.Optional; import java.util.function.BinaryOperator; import java.util.stream.Stream; +import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE; import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with; @@ -53,16 +54,16 @@ public class InsertTriggerGenerator { FOR row IN SELECT * FROM ${rawSuperTableName} LOOP - roleUuid := findRoleId(${rawSuperRoleDescriptor}(row)); + roleUuid := findRoleId(${rawSuperRoleDescriptor}); permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}'); - call grantPermissionToRole(roleUuid, permissionUuid); + call grantPermissionToRole(permissionUuid, roleUuid); END LOOP; END; $$; """, with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), - with("rawSuperRoleDescriptor", toVar(superRoleDef)) + with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, "row")) ); }); } @@ -79,50 +80,50 @@ public class InsertTriggerGenerator { strict as $$ begin call grantPermissionToRole( - ${rawSuperRoleDescriptor}(NEW), - createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}')); + createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'), + ${rawSuperRoleDescriptor}); return NEW; end; $$; - create trigger ${rawSubTableName}_${rawSuperTableName}_insert_tg + -- z_... is to put it at the end of after insert triggers, to make sure the roles exist + create trigger z_${rawSubTableName}_${rawSuperTableName}_insert_tg after insert on ${rawSuperTableName} for each row execute procedure ${rawSubTableName}_${rawSuperTableName}_insert_tf(); """, with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), - with("rawSuperRoleDescriptor", toVar(superRoleDef)) + with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, NEW.name())) ); }); } private void generateInsertCheckTrigger(final StringWriter plPgSql) { - plPgSql.writeLn(""" - /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. - */ - create or replace function ${rawSubTable}_insert_permission_missing_tf() - returns trigger - language plpgsql as $$ - begin - raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end; $$; - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); getOptionalInsertGrant().ifPresentOrElse(g -> { - plPgSql.writeLn(""" - create trigger ${rawSubTable}_insert_permission_check_tg - before insert on ${rawSubTable} - for each row - when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') ) - execute procedure ${rawSubTable}_insert_permission_missing_tf(); - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), - with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName() )); + if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) { + if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) { + generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g); + } else { + generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g); + } + } else { + switch (g.getSuperRoleDef().getRole()) { + case ADMIN -> { + generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql); + } + case GUEST -> { + // no permission check trigger generated, as anybody can insert rows into this table + } + default -> { + throw new IllegalArgumentException( + "invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole()); + } + } + } }, () -> { - plPgSql.writeLn(""" + plPgSql.writeLn(""" + -- FIXME: Where is this case necessary? create trigger ${rawSubTable}_insert_permission_check_tg before insert on ${rawSubTable} for each row @@ -135,6 +136,92 @@ public class InsertTriggerGenerator { }); } + private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') ) + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), + with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName())); + } + + private void generateInsertPermissionTriggerAllowByIndirectRole( + final StringWriter plPgSql, + final RbacView.RbacGrantDefinition g) { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + if ( not hasInsertPermission( + ( SELECT ${varName}.uuid FROM + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), + with("varName", g.getSuperRoleDef().getEntityAlias().aliasName())); + plPgSql.indented(3, () -> { + plPgSql.writeLn( + "(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}", + with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()), + with("ref", NEW.name())); + }); + plPgSql.writeLn(""" + + ), 'INSERT', '${rawSubTable}') ) then + raise exception + '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + } + + private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + when ( not isGlobalAdmin() ) + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + } + private Stream getInsertGrants() { return rbacDef.getGrantDefs().stream() .filter(g -> g.grantType() == PERM_TO_ROLE) @@ -162,4 +249,12 @@ public class InsertTriggerGenerator { return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName()); } + + private String toRoleDescriptor(final RbacView.RbacRoleDefinition roleDef, final String ref) { + final var functionName = toVar(roleDef); + if (roleDef.getEntityAlias().isGlobal()) { + return functionName + "()"; + } + return functionName + "(" + ref + ")"; + } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java index d664a83b..7e3c6a3b 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java @@ -31,7 +31,7 @@ public class RbacIdentityViewGenerator { $idName$); """; case SQL_QUERY -> """ - call generateRbacIdentityViewFromProjection('${rawTableName}', $idName$ + call generateRbacIdentityViewFromQuery('${rawTableName}', $idName$ ${identityViewSqlPart} $idName$); """; diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java index f8f6e890..a2d53d39 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java @@ -24,9 +24,11 @@ public class RbacRestrictedViewGenerator { --changeset ${liquibaseTagPrefix}-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('${rawTableName}', - '${orderBy}', + $orderBy$ + ${orderBy} + $orderBy$, $updates$ - ${updates} + ${updates} $updates$); --// diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 2d5cd93c..2ce71379 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -32,6 +32,7 @@ import java.util.stream.Stream; import static java.lang.reflect.Modifier.isStatic; import static java.util.Arrays.stream; import static java.util.Optional.ofNullable; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.autoFetched; import static org.apache.commons.lang3.StringUtils.uncapitalize; @@ -141,35 +142,42 @@ public class RbacView { if (rootEntityAliasProxy != null) { throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy); } - rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false); + rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL); return this; } public RbacView importSubEntityAlias( final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) { - importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, true); + importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, true, NOT_NULL); + return this; + } + + public RbacView importEntityAlias( + final String aliasName, final Class entityClass, + final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) { + importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, nullable); return this; } public RbacView importEntityAlias( final String aliasName, final Class entityClass, final Column dependsOnColum, final SQL fetchSql) { - importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false); + importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL); return this; } public RbacView importEntityAlias( final String aliasName, final Class entityClass, final Column dependsOnColum) { - importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false); + importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false, null); return this; } private EntityAlias importEntityAliasImpl( final String aliasName, final Class entityClass, - final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity) { - final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity); + final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) { + final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity, nullable); entityAliases.put(aliasName, entityAlias); try { importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity); @@ -281,15 +289,19 @@ public class RbacView { return RbacView.this; } - public RbacView grantPermission(final String entityAliasName, final Permission perm) { - final var entityAlias = findEntityAlias(entityAliasName); - final var forTable = entityAlias.getRawTableName(); - findOrCreateGrantDef(findRbacPerm(entityAlias, perm, forTable), superRoleDef).toCreate(); + public RbacView grantPermission(final Permission perm) { + final var forTable = rootEntityAlias.getRawTableName(); + findOrCreateGrantDef(findRbacPerm(rootEntityAlias, perm, forTable), superRoleDef).toCreate(); return RbacView.this; } } + public enum Nullable { + NOT_NULL, // DEFAULT + NULLABLE + } + @Getter @EqualsAndHashCode public class RbacGrantDefinition { @@ -560,14 +572,14 @@ public class RbacView { .orElseGet(() -> new RbacGrantDefinition(subRoleDefinition, superRoleDefinition)); } - record EntityAlias(String aliasName, Class entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity) { + record EntityAlias(String aliasName, Class entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) { public EntityAlias(final String aliasName) { - this(aliasName, null, null, null, false); + this(aliasName, null, null, null, false, null); } public EntityAlias(final String aliasName, final Class entityClass) { - this(aliasName, entityClass, null, null, false); + this(aliasName, entityClass, null, null, false, null); } boolean isGlobal() { @@ -626,39 +638,35 @@ public class RbacView { return tableName.substring(0, tableName.length() - "_rv".length()); } - public record Role(String roleName) { + public enum Role { - public static final Role OWNER = new Role("owner"); - public static final Role ADMIN = new Role("admin"); - public static final Role AGENT = new Role("agent"); - public static final Role TENANT = new Role("tenant"); - public static final Role REFERRER = new Role("referrer"); + OWNER, + ADMIN, + AGENT, + TENANT, + REFERRER, + + GUEST; @Override public String toString() { - return ":" + roleName; + return ":" + roleName(); } - @Override - public boolean equals(final Object obj) { - return ((obj instanceof Role) && ((Role) obj).roleName.equals(this.roleName)); + String roleName() { + return name().toLowerCase(); } } - public record Permission(String permission) { - - public static final Permission INSERT = new Permission("INSERT"); - public static final Permission DELETE = new Permission("DELETE"); - public static final Permission UPDATE = new Permission("UPDATE"); - public static final Permission SELECT = new Permission("SELECT"); - - public static Permission custom(final String permission) { - return new Permission(permission); - } + public enum Permission { + INSERT, + DELETE, + UPDATE, + SELECT; @Override public String toString() { - return ":" + permission; + return ":" + name(); } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java index eb8f3534..9850d942 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java @@ -37,8 +37,11 @@ public class RbacViewPostgresGenerator { @Override public String toString() { - return plPgSql.toString(); -} + return plPgSql.toString() + .replace("\n\n\n", "\n\n") + .replace("-- ====", "\n-- ====") + .replace("\n\n--//", "\n--//"); + } @SneakyThrows public void generateToChangeLog(final Path outputPath) { diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java index edb1f609..87a730a3 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -82,6 +82,7 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn("begin"); plPgSql.indented(() -> { plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);"); + plPgSql.writeLn(); generateCreateRolesAndGrantsAfterInsert(plPgSql); plPgSql.ensureSingleEmptyLine(); plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);"); @@ -90,6 +91,37 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn(); } + + private void generateSimplifiedUpdateTriggerFunction(final StringWriter plPgSql) { + + final var updateConditions = updatableEntityAliases() + .map(RbacView.EntityAlias::dependsOnColumName) + .distinct() + .map(columnName -> "NEW." + columnName + " is distinct from OLD." + columnName) + .collect(joining( "\n or ")); + plPgSql.writeLn(""" + /* + Called from the AFTER UPDATE TRIGGER to re-wire the grants. + */ + + create or replace procedure updateRbacRulesFor${simpleEntityName}( + OLD ${rawTableName}, + NEW ${rawTableName} + ) + language plpgsql as $$ + begin + + if ${updateConditions} then + delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; + call buildRbacSystemFor${simpleEntityName}(NEW); + end if; + end; $$; + """, + with("simpleEntityName", simpleEntityName), + with("rawTableName", rawTableName), + with("updateConditions", updateConditions)); + } + private void generateUpdateTriggerFunction(final StringWriter plPgSql) { plPgSql.writeLn(""" /* @@ -109,7 +141,7 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.chopEmptyLines(); plPgSql.indented(() -> { - updatableEntityAliases() + referencedEntityAliases() .forEach((ea) -> { plPgSql.writeLn(entityRefVar(OLD, ea) + " " + ea.getRawTableName() + ";"); plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";"); @@ -120,6 +152,7 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn("begin"); plPgSql.indented(() -> { plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);"); + plPgSql.writeLn(); generateUpdateRolesAndGrantsAfterUpdate(plPgSql); plPgSql.ensureSingleEmptyLine(); plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);"); @@ -132,11 +165,18 @@ class RolesGrantsAndPermissionsGenerator { return updatableEntityAliases().anyMatch(e -> true); } + private boolean hasAnyUpdatableAndNullableEntityAliases() { + return updatableEntityAliases() + .filter(ea -> ea.nullable() == RbacView.Nullable.NULLABLE) + .anyMatch(e -> true); + } + private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) { referencedEntityAliases() - .forEach((ea) -> plPgSql.writeLn( - ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";", - with("ref", NEW.name()))); + .forEach((ea) -> { + generateFetchedVars(plPgSql, ea, NEW); + plPgSql.writeLn(); + }); createRolesWithGrantsSql(plPgSql, OWNER); createRolesWithGrantsSql(plPgSql, ADMIN); @@ -165,14 +205,11 @@ class RolesGrantsAndPermissionsGenerator { private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) { plPgSql.ensureSingleEmptyLine(); - updatableEntityAliases() + referencedEntityAliases() .forEach((ea) -> { - plPgSql.writeLn( - ea.fetchSql().sql + " into " + entityRefVar(OLD, ea) + ";", - with("ref", OLD.name())); - plPgSql.writeLn( - ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";", - with("ref", NEW.name())); + generateFetchedVars(plPgSql, ea, OLD); + generateFetchedVars(plPgSql, ea, NEW); + plPgSql.writeLn(); }); updatableEntityAliases() @@ -190,6 +227,23 @@ class RolesGrantsAndPermissionsGenerator { }); } + private void generateFetchedVars( + final StringWriter plPgSql, + final RbacView.EntityAlias ea, + final PostgresTriggerReference old) { + plPgSql.writeLn( + ea.fetchSql().sql + " INTO " + entityRefVar(old, ea) + ";", + with("ref", old.name())); + if (ea.nullable() == RbacView.Nullable.NOT_NULL) { + plPgSql.writeLn( + "assert ${entityRefVar}.uuid is not null, format('${entityRefVar} must not be null for ${REF}.${dependsOnColumn} = %s', ${REF}.${dependsOnColumn});", + with("entityRefVar", entityRefVar(old, ea)), + with("dependsOnColumn", ea.dependsOnColumName()), + with("ref", old.name())); + plPgSql.writeLn(); + } + } + private boolean isUpdatable(final RbacView.Column c) { return rbacDef.getUpdatableColumns().contains(c); } @@ -222,7 +276,7 @@ class RolesGrantsAndPermissionsGenerator { .replace("${subRoleRef}", roleRef(OLD, grantDef.getSubRoleDef())) .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); case PERM_TO_ROLE -> "call revokePermissionFromRole(${permRef}, ${superRoleRef});" - .replace("${permRef}", findPerm(OLD, grantDef.getPermDef())) + .replace("${permRef}", getPerm(OLD, grantDef.getPermDef())) .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); }; } @@ -246,6 +300,10 @@ class RolesGrantsAndPermissionsGenerator { return permRef("findPermissionId", ref, permDef); } + private String getPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { + return permRef("getPermissionId", ref, permDef); + } + private String createPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { return permRef("createPermission", ref, permDef); } @@ -256,7 +314,7 @@ class RolesGrantsAndPermissionsGenerator { .replace("${entityRef}", rbacDef.isRootEntityAlias(permDef.entityAlias) ? ref.name() : refVarName(ref, permDef.entityAlias)) - .replace("${perm}", permDef.permission.permission()); + .replace("${perm}", permDef.permission.name()); } private String refVarName(final PostgresTriggerReference ref, final RbacView.EntityAlias entityAlias) { @@ -301,12 +359,12 @@ class RolesGrantsAndPermissionsGenerator { generatePermissionsForRole(plPgSql, role); - generateUserGrantsForRole(plPgSql, role); - generateIncomingSuperRolesForRole(plPgSql, role); generateOutgoingSubRolesForRole(plPgSql, role); + generateUserGrantsForRole(plPgSql, role); + plPgSql.chopTail(",\n"); plPgSql.writeLn(); }); @@ -333,7 +391,7 @@ class RolesGrantsAndPermissionsGenerator { final var arrayElements = permissionGrantsForRole.stream() .map(RbacView.RbacGrantDefinition::getPermDef) .map(RbacPermissionDefinition::getPermission) - .map(RbacView.Permission::permission) + .map(RbacView.Permission::name) .map(p -> "'" + p + "'") .sorted() .toList(); @@ -444,7 +502,11 @@ class RolesGrantsAndPermissionsGenerator { private void generateUpdateTrigger(final StringWriter plPgSql) { generateHeader(plPgSql, "update"); - generateUpdateTriggerFunction(plPgSql); + if ( hasAnyUpdatableAndNullableEntityAliases() ) { + generateSimplifiedUpdateTriggerFunction(plPgSql); + } else { + generateUpdateTriggerFunction(plPgSql); + } plPgSql.writeLn(""" /* diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java index 512ec72d..5a5e0699 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java @@ -38,12 +38,26 @@ public class StringWriter { --indentLevel; } + void indent(int levels) { + indentLevel += levels; + } + + void unindent(int levels) { + indentLevel -= levels; + } + void indented(final Runnable indented) { indent(); indented.run(); unindent(); } + void indented(int levels, final Runnable indented) { + indent(levels); + indented.run(); + unindent(levels); + } + boolean chopTail(final String tail) { if (string.toString().endsWith(tail)) { string.setLength(string.length() - tail.length()); @@ -103,8 +117,8 @@ public class StringWriter { text = matcher.replaceAll(varDef.value()); }); return text; - } catch (Exception exc) { - throw exc; + } catch (final RuntimeException exc) { + throw exc; // FIXME: just for debugging, remove try/catch before merging to master } } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java index 0296cd61..1f0ae950 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java @@ -21,6 +21,8 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService. @Service public class RbacGrantsDiagramService { + private static final int GRANT_LIMIT = 500; + public static void writeToFile(final String title, final String graph, final String fileName) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) { @@ -42,7 +44,11 @@ public class RbacGrantsDiagramService { PERMISSIONS, NOT_ASSUMED, TEST_ENTITIES, - NON_TEST_ENTITIES + NON_TEST_ENTITIES; + + public static final EnumSet ALL = EnumSet.allOf(Include.class); + public static final EnumSet ALL_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, TEST_ENTITIES, PERMISSIONS); + public static final EnumSet ALL_NON_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, NON_TEST_ENTITIES, PERMISSIONS); } @Autowired @@ -55,7 +61,7 @@ public class RbacGrantsDiagramService { private EntityManager em; public String allGrantsToCurrentUser(final EnumSet includes) { - final var graph = new HashSet(); + final var graph = new LimitedHashSet(); for ( UUID subjectUuid: context.currentSubjectsUuids() ) { traverseGrantsTo(graph, subjectUuid, includes); } @@ -65,6 +71,10 @@ public class RbacGrantsDiagramService { private void traverseGrantsTo(final Set graph, final UUID refUuid, final EnumSet includes) { final var grants = rawGrantRepo.findByAscendingUuid(refUuid); grants.forEach(g -> { + if ( g.getDescendantIdName() == null ) { + // FIXME: what's that? + return; + } if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) { return; } @@ -88,7 +98,7 @@ public class RbacGrantsDiagramService { .setParameter("targetObject", targetObject) .setParameter("op", op) .getSingleResult(); - final var graph = new HashSet(); + final var graph = new LimitedHashSet(); traverseGrantsFrom(graph, refUuid, includes); return toMermaidFlowchart(graph, includes); } @@ -116,7 +126,7 @@ public class RbacGrantsDiagramService { ) .collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName)) .entrySet().stream() - .map(entity -> "subgraph " + quoted(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n " + .map(entity -> "subgraph " + cleanId(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n " + entity.getValue().stream() .map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n ")) .sorted() @@ -127,14 +137,15 @@ public class RbacGrantsDiagramService { : ""; final var grants = graph.stream() - .map(g -> quoted(g.getAscendantIdName()) + .map(g -> cleanId(g.getAscendantIdName()) + " -->" + (g.isAssumed() ? " " : "|XX| ") - + quoted(g.getDescendantIdName())) + + cleanId(g.getDescendantIdName())) .sorted() .collect(joining("\n")); final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n"; return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "") + + (grants.length() > GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "") + "flowchart TB\n\n" + entities + grants; @@ -151,7 +162,7 @@ public class RbacGrantsDiagramService { // } // return "[" + table + "\n" + entity + "]"; // } - return "[" + entityId + "]"; + return "[" + cleanId(entityId) + "]"; } private static String renderEntityIdName(final Node node) { @@ -170,7 +181,7 @@ public class RbacGrantsDiagramService { } private String renderNode(final String idName, final UUID uuid) { - return quoted(idName) + renderNodeContent(idName, uuid); + return cleanId(idName) + renderNodeContent(idName, uuid); } private String renderNodeContent(final String idName, final UUID uuid) { @@ -196,9 +207,24 @@ public class RbacGrantsDiagramService { } @NotNull - private static String quoted(final String idName) { - return idName.replace(" ", ":").replaceAll("@.*", ""); + private static String cleanId(final String idName) { + return idName.replace(" ", ":").replaceAll("@.*", "") + .replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", ""); } + + + class LimitedHashSet extends HashSet { + + @Override + public boolean add(final T t) { + if (size() < GRANT_LIMIT ) { + return super.add(t); + } else { + return false; + } + } + } + } record Node(String idName, UUID uuid) { diff --git a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java index 6a031df7..fe053f1f 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java @@ -53,7 +53,7 @@ public class TestDomainEntity implements HasUuid { SELECT * FROM test_package p WHERE p.uuid= ${ref}.packageUuid """)) - .toRole("package", ADMIN).grantPermission("domain", INSERT) + .toRole("package", ADMIN).grantPermission(INSERT) .createRole(OWNER, (with) -> { with.incomingSuperRole("package", ADMIN); diff --git a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java index 757fcf05..9dc0d5d9 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java @@ -54,7 +54,7 @@ public class TestPackageEntity implements HasUuid { SELECT * FROM test_customer c WHERE c.uuid= ${ref}.customerUuid """)) - .toRole("customer", ADMIN).grantPermission("package", INSERT) + .toRole("customer", ADMIN).grantPermission(INSERT) .createRole(OWNER, (with) -> { with.incomingSuperRole("customer", ADMIN); diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 7770e470..99083bec 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.571772062. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:18.451453701. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 6ae19710..2d8436a8 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,6 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.584886824. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:18.467932975. + -- ============================================================================ --changeset test-customer-rbac-OBJECT:1 endDelimiter:--// @@ -36,8 +37,8 @@ begin perform createRoleWithGrants( testCustomerOwner(NEW), permissions => array['DELETE'], - userUuids => array[currentUserUuid()], - incomingSuperRoles => array[globalAdmin(unassumed())] + incomingSuperRoles => array[globalAdmin(unassumed())], + userUuids => array[currentUserUuid()] ); perform createRoleWithGrants( @@ -72,9 +73,9 @@ create trigger insertTriggerForTestCustomer_tg after insert on test_customer for each row execute procedure insertTriggerForTestCustomer_tf(); - --// + -- ============================================================================ --changeset test-customer-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -97,8 +98,8 @@ create trigger test_customer_insert_permission_check_tg -- only global admins are allowed to insert any rows. when ( not isGlobalAdmin() ) execute procedure test_customer_insert_permission_missing_tf(); - --// + -- ============================================================================ --changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -106,18 +107,19 @@ create trigger test_customer_insert_permission_check_tg call generateRbacIdentityViewFromProjection('test_customer', $idName$ prefix $idName$); - --// + -- ============================================================================ --changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('test_customer', - 'reference', + $orderBy$ + reference + $orderBy$, $updates$ - reference = new.reference, + reference = new.reference, prefix = new.prefix, adminUserName = new.adminUserName $updates$); --// - diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index 78da4439..2ba8560e 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.624847792. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:51.758424330. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 20562642..b3d20bac 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,6 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.625353859. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:51.767062425. + -- ============================================================================ --changeset test-package-rbac-OBJECT:1 endDelimiter:--// @@ -33,9 +34,12 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); + SELECT * FROM test_customer c WHERE c.uuid= NEW.customerUuid - into newCustomer; + INTO newCustomer; + assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid); + perform createRoleWithGrants( testPackageOwner(NEW), @@ -75,9 +79,9 @@ create trigger insertTriggerForTestPackage_tg after insert on test_package for each row execute procedure insertTriggerForTestPackage_tf(); - --// + -- ============================================================================ --changeset test-package-rbac-update-trigger:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -101,14 +105,18 @@ begin SELECT * FROM test_customer c WHERE c.uuid= OLD.customerUuid - into oldCustomer; + INTO oldCustomer; + assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid); + SELECT * FROM test_customer c WHERE c.uuid= NEW.customerUuid - into newCustomer; + INTO newCustomer; + assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid); + if NEW.customerUuid <> OLD.customerUuid then - call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer)); + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer)); call revokeRoleFromRole(testPackageOwner(OLD), testCustomerAdmin(oldCustomer)); call grantRoleToRole(testPackageOwner(NEW), testCustomerAdmin(newCustomer)); @@ -138,9 +146,9 @@ create trigger updateTriggerForTestPackage_tg after update on test_package for each row execute procedure updateTriggerForTestPackage_tf(); - --// + -- ============================================================================ --changeset test-package-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -160,7 +168,7 @@ do language plpgsql $$ LOOP roleUuid := findRoleId(testCustomerAdmin(row)); permissionUuid := createPermission(row.uuid, 'INSERT', 'test_package'); - call grantPermissionToRole(roleUuid, permissionUuid); + call grantPermissionToRole(permissionUuid, roleUuid); END LOOP; END; $$; @@ -174,12 +182,13 @@ create or replace function test_package_test_customer_insert_tf() strict as $$ begin call grantPermissionToRole( - testCustomerAdmin(NEW), - createPermission(NEW.uuid, 'INSERT', 'test_package')); + createPermission(NEW.uuid, 'INSERT', 'test_package'), + testCustomerAdmin(NEW)); return NEW; end; $$; -create trigger test_package_test_customer_insert_tg +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_test_package_test_customer_insert_tg after insert on test_customer for each row execute procedure test_package_test_customer_insert_tf(); @@ -191,17 +200,27 @@ create or replace function test_package_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - raise exception '[403] insert into test_package not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); + if ( not hasInsertPermission( + ( SELECT customer.uuid FROM + + (SELECT * FROM test_customer c + WHERE c.uuid= NEW.customerUuid + ) AS customer + + ), 'INSERT', 'test_package') ) then + raise exception + '[403] insert into test_package not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; end; $$; create trigger test_package_insert_permission_check_tg before insert on test_package for each row - when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') ) execute procedure test_package_insert_permission_missing_tf(); - --// + -- ============================================================================ --changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -209,18 +228,19 @@ create trigger test_package_insert_permission_check_tg call generateRbacIdentityViewFromProjection('test_package', $idName$ name $idName$); - --// + -- ============================================================================ --changeset test-package-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('test_package', - 'name', + $orderBy$ + name + $orderBy$, $updates$ - version = new.version, + version = new.version, customerUuid = new.customerUuid, description = new.description $updates$); --// - diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index bd5cf706..800a6fe5 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.644658132. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:31.860490657. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index e686dada..6fb9cc73 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,6 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.645391647. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:31.873124905. + -- ============================================================================ --changeset test-domain-rbac-OBJECT:1 endDelimiter:--// @@ -33,9 +34,12 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); + SELECT * FROM test_package p WHERE p.uuid= NEW.packageUuid - into newPackage; + INTO newPackage; + assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid); + perform createRoleWithGrants( testDomainOwner(NEW), @@ -71,9 +75,9 @@ create trigger insertTriggerForTestDomain_tg after insert on test_domain for each row execute procedure insertTriggerForTestDomain_tf(); - --// + -- ============================================================================ --changeset test-domain-rbac-update-trigger:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -97,14 +101,18 @@ begin SELECT * FROM test_package p WHERE p.uuid= OLD.packageUuid - into oldPackage; + INTO oldPackage; + assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid); + SELECT * FROM test_package p WHERE p.uuid= NEW.packageUuid - into newPackage; + INTO newPackage; + assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid); + if NEW.packageUuid <> OLD.packageUuid then - call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage)); + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage)); call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage)); call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage)); @@ -137,9 +145,9 @@ create trigger updateTriggerForTestDomain_tg after update on test_domain for each row execute procedure updateTriggerForTestDomain_tf(); - --// + -- ============================================================================ --changeset test-domain-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -159,7 +167,7 @@ do language plpgsql $$ LOOP roleUuid := findRoleId(testPackageAdmin(row)); permissionUuid := createPermission(row.uuid, 'INSERT', 'test_domain'); - call grantPermissionToRole(roleUuid, permissionUuid); + call grantPermissionToRole(permissionUuid, roleUuid); END LOOP; END; $$; @@ -173,12 +181,13 @@ create or replace function test_domain_test_package_insert_tf() strict as $$ begin call grantPermissionToRole( - testPackageAdmin(NEW), - createPermission(NEW.uuid, 'INSERT', 'test_domain')); + createPermission(NEW.uuid, 'INSERT', 'test_domain'), + testPackageAdmin(NEW)); return NEW; end; $$; -create trigger test_domain_test_package_insert_tg +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_test_domain_test_package_insert_tg after insert on test_package for each row execute procedure test_domain_test_package_insert_tf(); @@ -190,17 +199,27 @@ create or replace function test_domain_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - raise exception '[403] insert into test_domain not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); + if ( not hasInsertPermission( + ( SELECT package.uuid FROM + + (SELECT * FROM test_package p + WHERE p.uuid= NEW.packageUuid + ) AS package + + ), 'INSERT', 'test_domain') ) then + raise exception + '[403] insert into test_domain not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; end; $$; create trigger test_domain_insert_permission_check_tg before insert on test_domain for each row - when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') ) execute procedure test_domain_insert_permission_missing_tf(); - --// + -- ============================================================================ --changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -208,18 +227,19 @@ create trigger test_domain_insert_permission_check_tg call generateRbacIdentityViewFromProjection('test_domain', $idName$ name $idName$); - --// + -- ============================================================================ --changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('test_domain', - 'name', + $orderBy$ + name + $orderBy$, $updates$ - version = new.version, + version = new.version, packageUuid = new.packageUuid, description = new.description $updates$); --// - From 3551ef087bfbe0db683f884806d978ff144165f6 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 09:48:39 +0100 Subject: [PATCH 4/8] fix RbacGrantsDiagramService grant limit treatment --- .../hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java index 1f0ae950..e643fe8d 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java @@ -145,7 +145,7 @@ public class RbacGrantsDiagramService { final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n"; return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "") - + (grants.length() > GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "") + + (graph.size() >= GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "") + "flowchart TB\n\n" + entities + grants; From 20cc98b48e005c8c04f9eb7330db1c8608761722 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 10:00:16 +0100 Subject: [PATCH 5/8] reverse arguments of grantPermissionToRole according to reading order --- src/main/resources/db/changelog/050-rbac-base.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql index 2992d6a9..0c64866c 100644 --- a/src/main/resources/db/changelog/050-rbac-base.sql +++ b/src/main/resources/db/changelog/050-rbac-base.sql @@ -588,7 +588,7 @@ select exists( ); $$; -create or replace procedure grantPermissionToRole(roleUuid uuid, permissionUuid uuid) +create or replace procedure grantPermissionToRole(permissionUuid uuid, roleUuid uuid) language plpgsql as $$ begin perform assertReferenceType('roleId (ascendant)', roleUuid, 'RbacRole'); @@ -601,10 +601,10 @@ begin end; $$; -create or replace procedure grantPermissionToRole(roleDesc RbacRoleDescriptor, permissionUuid uuid) +create or replace procedure grantPermissionToRole(permissionUuid uuid, roleDesc RbacRoleDescriptor) language plpgsql as $$ begin - call grantPermissionToRole(findRoleId(roleDesc), permissionUuid); + call grantPermissionToRole(permissionUuid, findRoleId(roleDesc)); end; $$; From a991c45bc95243161611351a7ee083d1b91a0b8b Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 11:28:56 +0100 Subject: [PATCH 6/8] code improvements and documentation in InsertTriggerGenerator --- .../rbac/rbacdef/InsertTriggerGenerator.java | 31 ++++++++++++------- .../db/changelog/113-test-customer-rbac.md | 2 +- .../db/changelog/113-test-customer-rbac.sql | 14 ++------- .../db/changelog/123-test-package-rbac.md | 2 +- .../db/changelog/123-test-package-rbac.sql | 7 +++-- .../db/changelog/133-test-domain-rbac.md | 2 +- .../db/changelog/133-test-domain-rbac.sql | 7 +++-- 7 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index 88d07efa..faa3d565 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -23,7 +23,7 @@ public class InsertTriggerGenerator { void generateTo(final StringWriter plPgSql) { generateLiquibaseChangesetHeader(plPgSql); - generateGrantInsertRoleToExistingCustomers(plPgSql); + generateGrantInsertRoleToExistingObjects(plPgSql); generateInsertPermissionGrantTrigger(plPgSql); generateInsertCheckTrigger(plPgSql); plPgSql.writeLn("--//"); @@ -38,7 +38,7 @@ public class InsertTriggerGenerator { with("liquibaseTagPrefix", liquibaseTagPrefix)); } - private void generateGrantInsertRoleToExistingCustomers(final StringWriter plPgSql) { + private void generateGrantInsertRoleToExistingObjects(final StringWriter plPgSql) { getOptionalInsertSuperRole().ifPresent( superRoleDef -> { plPgSql.writeLn(""" /* @@ -100,13 +100,7 @@ public class InsertTriggerGenerator { private void generateInsertCheckTrigger(final StringWriter plPgSql) { getOptionalInsertGrant().ifPresentOrElse(g -> { - if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) { - if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) { - generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g); - } else { - generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g); - } - } else { + if (g.getSuperRoleDef().getEntityAlias().isGlobal()) { switch (g.getSuperRoleDef().getRole()) { case ADMIN -> { generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql); @@ -119,6 +113,12 @@ public class InsertTriggerGenerator { "invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole()); } } + } else { + if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) { + generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g); + } else { + generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g); + } } }, () -> { @@ -139,7 +139,10 @@ public class InsertTriggerGenerator { private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { plPgSql.writeLn(""" /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, + 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 ${rawSubTable}_insert_permission_missing_tf() returns trigger @@ -164,7 +167,10 @@ public class InsertTriggerGenerator { final RbacView.RbacGrantDefinition g) { plPgSql.writeLn(""" /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, + where the check is performed by an indirect role. + + An indirect role is a role FIXME. */ create or replace function ${rawSubTable}_insert_permission_missing_tf() returns trigger @@ -203,7 +209,8 @@ public class InsertTriggerGenerator { private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { plPgSql.writeLn(""" /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, + where only global-admin has that permission. */ create or replace function ${rawSubTable}_insert_permission_missing_tf() returns trigger diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 99083bec..14057c2a 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:18.451453701. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.310302721. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 2d8436a8..2b3fda9f 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:18.467932975. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.329089492. -- ============================================================================ @@ -80,17 +80,7 @@ execute procedure insertTriggerForTestCustomer_tf(); --changeset test-customer-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -/** - Checks if the user or assumed roles are allowed to insert a row to test_customer. -*/ -create or replace function test_customer_insert_permission_missing_tf() - returns trigger - language plpgsql as $$ -begin - raise exception '[403] insert into test_customer not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); -end; $$; - +-- FIXME: Where is this case necessary? create trigger test_customer_insert_permission_check_tg before insert on test_customer for each row diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index 2ba8560e..0ca13fc4 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:51.758424330. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.365161640. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index b3d20bac..1e79ac4b 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:51.767062425. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.365610181. -- ============================================================================ @@ -194,7 +194,10 @@ create trigger z_test_package_test_customer_insert_tg execute procedure test_package_test_customer_insert_tf(); /** - Checks if the user or assumed roles are allowed to insert a row to test_package. + Checks if the user or assumed roles are allowed to insert a row to test_package, + where the check is performed by an indirect role. + + An indirect role is a role FIXME. */ create or replace function test_package_insert_permission_missing_tf() returns trigger diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index 800a6fe5..71fb6074 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:31.860490657. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.391784384. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index 6fb9cc73..8bd6b8df 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:31.873124905. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.392306652. -- ============================================================================ @@ -193,7 +193,10 @@ create trigger z_test_domain_test_package_insert_tg execute procedure test_domain_test_package_insert_tf(); /** - Checks if the user or assumed roles are allowed to insert a row to test_domain. + Checks if the user or assumed roles are allowed to insert a row to test_domain, + where the check is performed by an indirect role. + + An indirect role is a role FIXME. */ create or replace function test_domain_insert_permission_missing_tf() returns trigger From e118cfac731ab3f48402ead0141764ccedcd3af8 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 13:49:45 +0100 Subject: [PATCH 7/8] simplify InsertTriggerGenerator cases --- .../rbac/rbacdef/InsertTriggerGenerator.java | 65 ++----------------- .../rbacdef/RbacRestrictedViewGenerator.java | 4 +- .../hsadminng/rbac/rbacdef/RbacView.java | 2 +- .../db/changelog/113-test-customer-rbac.md | 2 +- .../db/changelog/113-test-customer-rbac.sql | 19 ++++-- .../db/changelog/123-test-package-rbac.md | 2 +- .../db/changelog/123-test-package-rbac.sql | 24 ++----- .../db/changelog/133-test-domain-rbac.md | 2 +- .../db/changelog/133-test-domain-rbac.sql | 24 ++----- 9 files changed, 37 insertions(+), 107 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index faa3d565..d625c0d7 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -114,29 +114,16 @@ public class InsertTriggerGenerator { } } } else { - if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) { - generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g); - } else { - generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g); - } + generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); } }, () -> { - plPgSql.writeLn(""" - -- FIXME: Where is this case necessary? - create trigger ${rawSubTable}_insert_permission_check_tg - before insert on ${rawSubTable} - for each row - -- As there is no explicit INSERT grant specified for this table, - -- only global admins are allowed to insert any rows. - when ( not isGlobalAdmin() ) - execute procedure ${rawSubTable}_insert_permission_missing_tf(); - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + System.err.println("WARNING: no explicit INSERT grant for " + rbacDef.getRootEntityAlias().simpleName() + " => implicitly grant INSERT to global.admin"); + generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql); }); } - private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { + private void generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { plPgSql.writeLn(""" /** Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, @@ -162,50 +149,6 @@ public class InsertTriggerGenerator { with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName())); } - private void generateInsertPermissionTriggerAllowByIndirectRole( - final StringWriter plPgSql, - final RbacView.RbacGrantDefinition g) { - plPgSql.writeLn(""" - /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, - where the check is performed by an indirect role. - - An indirect role is a role FIXME. - */ - create or replace function ${rawSubTable}_insert_permission_missing_tf() - returns trigger - language plpgsql as $$ - begin - if ( not hasInsertPermission( - ( SELECT ${varName}.uuid FROM - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), - with("varName", g.getSuperRoleDef().getEntityAlias().aliasName())); - plPgSql.indented(3, () -> { - plPgSql.writeLn( - "(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}", - with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()), - with("ref", NEW.name())); - }); - plPgSql.writeLn(""" - - ), 'INSERT', '${rawSubTable}') ) then - raise exception - '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end if; - return NEW; - end; $$; - - create trigger ${rawSubTable}_insert_permission_check_tg - before insert on ${rawSubTable} - for each row - execute procedure ${rawSubTable}_insert_permission_missing_tf(); - - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); - } - private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { plPgSql.writeLn(""" /** diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java index a2d53d39..0a6d23ac 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java @@ -8,13 +8,11 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with; public class RbacRestrictedViewGenerator { private final RbacView rbacDef; private final String liquibaseTagPrefix; - private final String simpleEntityVarName; private final String rawTableName; public RbacRestrictedViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) { this.rbacDef = rbacDef; this.liquibaseTagPrefix = liquibaseTagPrefix; - this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName(); this.rawTableName = rbacDef.getRootEntityAlias().getRawTableName(); } @@ -28,7 +26,7 @@ public class RbacRestrictedViewGenerator { ${orderBy} $orderBy$, $updates$ - ${updates} + ${updates} $updates$); --// diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 2ce71379..57499bb2 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -831,7 +831,7 @@ public class RbacView { throw new RuntimeException(e); } } else { - System.err.println("no main method in: " + c.getName()); + System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated"); } }); } diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 14057c2a..997c5e97 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.310302721. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.041634681. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 2b3fda9f..9472f71e 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.329089492. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.059763974. -- ============================================================================ @@ -80,12 +80,21 @@ execute procedure insertTriggerForTestCustomer_tf(); --changeset test-customer-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- --- FIXME: Where is this case necessary? +/** + Checks if the user or assumed roles are allowed to insert a row to test_customer, + where only global-admin has that permission. +*/ +create or replace function test_customer_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into test_customer not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + create trigger test_customer_insert_permission_check_tg before insert on test_customer for each row - -- As there is no explicit INSERT grant specified for this table, - -- only global admins are allowed to insert any rows. when ( not isGlobalAdmin() ) execute procedure test_customer_insert_permission_missing_tf(); --// @@ -107,7 +116,7 @@ call generateRbacRestrictedView('test_customer', reference $orderBy$, $updates$ - reference = new.reference, + reference = new.reference, prefix = new.prefix, adminUserName = new.adminUserName $updates$); diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index 0ca13fc4..b4281d6d 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.365161640. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.107522933. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 1e79ac4b..26a2d1af 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.365610181. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.108087056. -- ============================================================================ @@ -195,32 +195,22 @@ execute procedure test_package_test_customer_insert_tf(); /** Checks if the user or assumed roles are allowed to insert a row to test_package, - where the check is performed by an indirect role. + where the check is performed by a direct role. - An indirect role is a role FIXME. + A direct role is a role depending on a foreign key directly available in the NEW row. */ create or replace function test_package_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - if ( not hasInsertPermission( - ( SELECT customer.uuid FROM - - (SELECT * FROM test_customer c - WHERE c.uuid= NEW.customerUuid - ) AS customer - - ), 'INSERT', 'test_package') ) then - raise exception - '[403] insert into test_package not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end if; - return NEW; + raise exception '[403] insert into test_package not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); end; $$; create trigger test_package_insert_permission_check_tg before insert on test_package for each row + when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') ) execute procedure test_package_insert_permission_missing_tf(); --// @@ -241,7 +231,7 @@ call generateRbacRestrictedView('test_package', name $orderBy$, $updates$ - version = new.version, + version = new.version, customerUuid = new.customerUuid, description = new.description $updates$); diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index 71fb6074..f856c2f7 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.391784384. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.134524089. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index 8bd6b8df..c98a9bb4 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.392306652. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.134964758. -- ============================================================================ @@ -194,32 +194,22 @@ execute procedure test_domain_test_package_insert_tf(); /** Checks if the user or assumed roles are allowed to insert a row to test_domain, - where the check is performed by an indirect role. + where the check is performed by a direct role. - An indirect role is a role FIXME. + A direct role is a role depending on a foreign key directly available in the NEW row. */ create or replace function test_domain_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - if ( not hasInsertPermission( - ( SELECT package.uuid FROM - - (SELECT * FROM test_package p - WHERE p.uuid= NEW.packageUuid - ) AS package - - ), 'INSERT', 'test_domain') ) then - raise exception - '[403] insert into test_domain not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end if; - return NEW; + raise exception '[403] insert into test_domain not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); end; $$; create trigger test_domain_insert_permission_check_tg before insert on test_domain for each row + when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') ) execute procedure test_domain_insert_permission_missing_tf(); --// @@ -240,7 +230,7 @@ call generateRbacRestrictedView('test_domain', name $orderBy$, $updates$ - version = new.version, + version = new.version, packageUuid = new.packageUuid, description = new.description $updates$); From 83b16dfe5ed8649012798fa73ba3bc7f5f0bfe14 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 14:46:29 +0100 Subject: [PATCH 8/8] imroved indentation --- .../hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java | 6 +++--- src/main/resources/db/changelog/113-test-customer-rbac.md | 2 +- src/main/resources/db/changelog/113-test-customer-rbac.sql | 4 ++-- src/main/resources/db/changelog/123-test-package-rbac.md | 2 +- src/main/resources/db/changelog/123-test-package-rbac.sql | 4 ++-- src/main/resources/db/changelog/133-test-domain-rbac.md | 2 +- src/main/resources/db/changelog/133-test-domain-rbac.sql | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java index 0a6d23ac..f0bd50a0 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java @@ -23,16 +23,16 @@ public class RbacRestrictedViewGenerator { -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('${rawTableName}', $orderBy$ - ${orderBy} + ${orderBy} $orderBy$, $updates$ - ${updates} + ${updates} $updates$); --// """, with("liquibaseTagPrefix", liquibaseTagPrefix), - with("orderBy", rbacDef.getOrderBySqlExpression().sql), + with("orderBy", indented(rbacDef.getOrderBySqlExpression().sql, 2)), with("updates", indented(rbacDef.getUpdatableColumns().stream() .map(c -> c + " = new." + c) .collect(joining(",\n")), 2)), diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 997c5e97..438b6254 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.041634681. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.425403022. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 9472f71e..dccb3a26 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.059763974. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.441879428. -- ============================================================================ @@ -116,7 +116,7 @@ call generateRbacRestrictedView('test_customer', reference $orderBy$, $updates$ - reference = new.reference, + reference = new.reference, prefix = new.prefix, adminUserName = new.adminUserName $updates$); diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index b4281d6d..895d3269 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.107522933. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.484173294. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 26a2d1af..fd375e2d 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.108087056. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.484728385. -- ============================================================================ @@ -231,7 +231,7 @@ call generateRbacRestrictedView('test_package', name $orderBy$, $updates$ - version = new.version, + version = new.version, customerUuid = new.customerUuid, description = new.description $updates$); diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index f856c2f7..4f507312 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.134524089. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.510830235. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index c98a9bb4..511b2de4 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.134964758. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.511320177. -- ============================================================================ @@ -230,7 +230,7 @@ call generateRbacRestrictedView('test_domain', name $orderBy$, $updates$ - version = new.version, + version = new.version, packageUuid = new.packageUuid, description = new.description $updates$);