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 5c7bb69d..7418962f 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 @@ -12,6 +12,8 @@ import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.JoinFormula; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; import jakarta.persistence.*; import java.io.IOException; @@ -65,6 +67,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { WHERE pRel.relHolderUuid = dRel.relAnchorUuid ) """) + @NotFound(action = NotFoundAction.IGNORE) private HsOfficePartnerEntity partner; @Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)") @@ -160,8 +163,8 @@ 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 + FROM hs_office_bankaccount AS b + WHERE b.uuid = ${REF}.refundBankAccountUuid """), NULLABLE ) 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 26493107..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))) { @@ -59,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); } @@ -96,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); } @@ -143,6 +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" : "") + "flowchart TB\n\n" + entities + grants; @@ -208,6 +211,20 @@ public class RbacGrantsDiagramService { 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/resources/db/changelog/273-hs-office-debitor-rbac.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql index 4f63a94e..bb551a0a 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql @@ -53,8 +53,8 @@ begin assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid); SELECT * - FROM hs_office_relationship AS r - WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = NEW.debitorRelUuid + FROM hs_office_bankaccount AS b + WHERE b.uuid = NEW.refundbankaccountuuid INTO newRefundBankAccount; call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationshipAgent(newDebitorRel)); @@ -113,103 +113,9 @@ declare newRefundBankAccount hs_office_bankaccount; begin - call enterTriggerForObjectUuid(NEW.uuid); + delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; - SELECT partnerRel.* - FROM hs_office_relationship AS partnerRel - JOIN hs_office_relationship AS debitorRel - ON debitorRel.relType = 'ACCOUNTING' AND debitorRel.relAnchorUuid = partnerRel.relHolderUuid - WHERE partnerRel.relType = 'PARTNER' - AND OLD.debitorRelUuid = debitorRel.uuid - INTO oldPartnerRel; - assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.debitorRelUuid = %s', OLD.debitorRelUuid); - - SELECT partnerRel.* - FROM hs_office_relationship AS partnerRel - JOIN hs_office_relationship AS debitorRel - ON debitorRel.relType = 'ACCOUNTING' AND debitorRel.relAnchorUuid = partnerRel.relHolderUuid - WHERE partnerRel.relType = 'PARTNER' - AND NEW.debitorRelUuid = debitorRel.uuid - INTO newPartnerRel; - assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid); - - SELECT * - FROM hs_office_relationship AS r - WHERE r.relType = 'ACCOUNTING' AND r.uuid = OLD.debitorRelUuid - INTO oldDebitorRel; - assert oldDebitorRel.uuid is not null, format('oldDebitorRel must not be null for OLD.debitorRelUuid = %s', OLD.debitorRelUuid); - - SELECT * - FROM hs_office_relationship AS r - WHERE r.relType = 'ACCOUNTING' AND r.uuid = NEW.debitorRelUuid - INTO newDebitorRel; - assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid); - - SELECT * - FROM hs_office_relationship AS r - WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = OLD.debitorRelUuid - INTO oldRefundBankAccount; - SELECT * - FROM hs_office_relationship AS r - WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = NEW.debitorRelUuid - INTO newRefundBankAccount; - - if NEW.debitorRelUuid <> OLD.debitorRelUuid then - assert OLD.uuid=NEW.uuid, 'NEW vs. OLD uuids must be equal'; - - call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationshipOwner(oldDebitorRel)); - call grantPermissionToRole(getPermissionId(NEW.uuid, 'DELETE'), hsOfficeRelationshipOwner(newDebitorRel)); - - call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationshipAdmin(oldDebitorRel)); - call grantPermissionToRole(getPermissionId(NEW.uuid, 'UPDATE'), hsOfficeRelationshipAdmin(newDebitorRel)); - - call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationshipTenant(oldDebitorRel)); - call grantPermissionToRole(getPermissionId(NEW.uuid, 'SELECT'), hsOfficeRelationshipTenant(newDebitorRel)); - - if oldRefundBankAccount is not null then - call revokeRoleFromRole(hsOfficeRelationshipAgent(oldDebitorRel), hsOfficeBankAccountAdmin(oldRefundBankAccount)); - end if; - if newRefundBankAccount is not null then - call grantRoleToRole(hsOfficeRelationshipAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount)); - end if; - - if oldRefundBankAccount is not null then - call revokeRoleFromRole(hsOfficeBankAccountReferrer(oldRefundBankAccount), hsOfficeRelationshipAgent(oldDebitorRel)); - end if; - if newRefundBankAccount is not null then - call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationshipAgent(newDebitorRel)); - end if; - - call revokeRoleFromRole(hsOfficeRelationshipAdmin(oldDebitorRel), hsOfficeRelationshipAdmin(oldPartnerRel)); - call grantRoleToRole(hsOfficeRelationshipAdmin(newDebitorRel), hsOfficeRelationshipAdmin(newPartnerRel)); - - call revokeRoleFromRole(hsOfficeRelationshipAgent(oldDebitorRel), hsOfficeRelationshipAgent(oldPartnerRel)); - call grantRoleToRole(hsOfficeRelationshipAgent(newDebitorRel), hsOfficeRelationshipAgent(newPartnerRel)); - - call revokeRoleFromRole(hsOfficeRelationshipTenant(oldPartnerRel), hsOfficeRelationshipAgent(oldDebitorRel)); - call grantRoleToRole(hsOfficeRelationshipTenant(newPartnerRel), hsOfficeRelationshipAgent(newDebitorRel)); - - end if; - - if NEW.refundBankAccountUuid <> OLD.refundBankAccountUuid then - - if oldRefundBankAccount is not null then - call revokeRoleFromRole(hsOfficeRelationshipAgent(oldDebitorRel), hsOfficeBankAccountAdmin(oldRefundBankAccount)); - end if; - if newRefundBankAccount is not null then - call grantRoleToRole(hsOfficeRelationshipAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount)); - end if; - - if oldRefundBankAccount is not null then - call revokeRoleFromRole(hsOfficeBankAccountReferrer(oldRefundBankAccount), hsOfficeRelationshipAgent(oldDebitorRel)); - end if; - if newRefundBankAccount is not null then - call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationshipAgent(newDebitorRel)); - end if; - - end if; - - call leaveTriggerForObjectUuid(NEW.uuid); + call buildRbacSystemForHsOfficeDebitor(NEW); end; $$; /* diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index ef556e03..204ed771 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -319,6 +319,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean givenDebitor, "hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin"); final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First")); + final var givenNewBillingPerson = one(personRepo.findPersonByOptionalNameLike("Firby")); final var givenNewContact = one(contactRepo.findContactByOptionalLabelLike("sixth contact")); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); final String givenNewVatId = "NEW-VAT-ID"; @@ -331,7 +332,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean givenDebitor.setDebitorRel(HsOfficeRelationshipEntity.builder() .relType(HsOfficeRelationshipType.ACCOUNTING) .relAnchor(givenNewPartnerPerson) - .relHolder(givenNewPartnerPerson) + .relHolder(givenNewBillingPerson) .contact(givenNewContact) .build()); givenDebitor.setRefundBankAccount(givenNewBankAccount); @@ -353,7 +354,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean "hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin"); assertThatDebitorIsVisibleForUserWithRole( result.returnedValue(), - "hs_office_relationship#FirstGmbH-with-ACCOUNTING-FirstGmbH.agent"); + "hs_office_relationship#FirstGmbH-with-ACCOUNTING-FirbySusan.agent"); // ... contact role was reassigned: assertThatDebitorIsNotVisibleForUserWithRole( @@ -366,10 +367,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // ... bank-account role was reassigned: assertThatDebitorIsNotVisibleForUserWithRole( result.returnedValue(), - "hs_office_bankaccount#FourtheG.admin"); + "hs_office_bankaccount#DE02200505501015871393.admin"); assertThatDebitorIsVisibleForUserWithRole( result.returnedValue(), - "hs_office_bankaccount#FirstGmbH.admin"); + "hs_office_bankaccount#DE02120300000000202051.admin"); } @Test @@ -379,7 +380,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null, "fig"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office_partner#10004:FourtheG-fourthcontact.admin"); + "hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin"); assertThatDebitorActuallyInDatabase(givenDebitor); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); @@ -399,7 +400,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // ... bank-account role was assigned: assertThatDebitorIsVisibleForUserWithRole( result.returnedValue(), - "hs_office_bankaccount#FirstGmbH.admin"); + "hs_office_bankaccount#DE02120300000000202051.admin"); } @Test @@ -409,7 +410,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fih"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office_partner#10004:FourtheG-fourthcontact.admin"); + "hs_office_relationship#HostsharingeG-with-PARTNER-FourtheG.agent"); assertThatDebitorActuallyInDatabase(givenDebitor); // when @@ -428,33 +429,29 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // ... bank-account role was removed from previous bank-account admin: assertThatDebitorIsNotVisibleForUserWithRole( result.returnedValue(), - "hs_office_bankaccount#FourtheG.admin"); + "hs_office_bankaccount#DE02200505501015871393.admin"); } @Test - public void partnerAdmin_canNotUpdateRelatedDebitor() { + public void partnerAgent_canNotUpdateRelatedDebitor() { // given context("superuser-alex@hostsharing.net"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth", "eig"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office_relationship#HostsharingeG-with-PARTNER-FourtheG.admin"); + "hs_office_relationship#HostsharingeG-with-PARTNER-FourtheG.agent"); assertThatDebitorActuallyInDatabase(givenDebitor); // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office_relationship#HostsharingeG-with-PARTNER-FourtheG.admin"); + context("superuser-alex@hostsharing.net", "hs_office_relationship#HostsharingeG-with-PARTNER-FourtheG.agent"); givenDebitor.setVatId("NEW-VAT-ID"); return toCleanup(debitorRepo.save(givenDebitor)); }); // then -// FIXME: This error message would be better: -// result.assertExceptionWithRootCauseMessage(JpaSystemException.class, -// "[403] Subject ", " is not allowed to update hs_office_debitor uuid"); - result.assertExceptionWithRootCauseMessage( - JpaObjectRetrievalFailureException.class, - "Unable to find net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity with id "); + result.assertExceptionWithRootCauseMessage(JpaSystemException.class, + "[403] Subject ", " is not allowed to update hs_office_debitor uuid"); } @Test @@ -489,7 +486,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean found.ifPresent(foundEntity -> { em.refresh(foundEntity); assertThat(foundEntity).isNotSameAs(saved); - //assertThat(foundEntity.getPartner()).isNotNull(); + if ( saved.getPartner() != null) { // FIXME: check, why there is no partner for the updated contact + assertThat(foundEntity.getPartner()).isNotNull(); + } assertThat(foundEntity.getDebitorRel()).extracting(HsOfficeRelationshipEntity::toString) .isEqualTo(saved.getDebitorRel().toString()); });