HsOfficeDebitorRepositoryIntegrationTest green

This commit is contained in:
Michael Hoennig 2024-03-20 08:52:17 +01:00
parent 74b20ed86c
commit bb3f979273
4 changed files with 45 additions and 120 deletions

View File

@ -12,6 +12,8 @@ import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.JoinFormula; import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.io.IOException; import java.io.IOException;
@ -65,6 +67,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
WHERE pRel.relHolderUuid = dRel.relAnchorUuid WHERE pRel.relHolderUuid = dRel.relAnchorUuid
) )
""") """)
@NotFound(action = NotFoundAction.IGNORE)
private HsOfficePartnerEntity partner; private HsOfficePartnerEntity partner;
@Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)") @Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)")
@ -160,8 +163,8 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
dependsOnColumn("refundBankAccountUuid"), dependsOnColumn("refundBankAccountUuid"),
fetchedBySql(""" fetchedBySql("""
SELECT * SELECT *
FROM hs_office_relationship AS r FROM hs_office_bankaccount AS b
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid WHERE b.uuid = ${REF}.refundBankAccountUuid
"""), """),
NULLABLE NULLABLE
) )

View File

@ -21,6 +21,8 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.
@Service @Service
public class RbacGrantsDiagramService { public class RbacGrantsDiagramService {
private static final int GRANT_LIMIT = 500;
public static void writeToFile(final String title, final String graph, final String fileName) { public static void writeToFile(final String title, final String graph, final String fileName) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
@ -59,7 +61,7 @@ public class RbacGrantsDiagramService {
private EntityManager em; private EntityManager em;
public String allGrantsToCurrentUser(final EnumSet<Include> includes) { public String allGrantsToCurrentUser(final EnumSet<Include> includes) {
final var graph = new HashSet<RawRbacGrantEntity>(); final var graph = new LimitedHashSet<RawRbacGrantEntity>();
for ( UUID subjectUuid: context.currentSubjectsUuids() ) { for ( UUID subjectUuid: context.currentSubjectsUuids() ) {
traverseGrantsTo(graph, subjectUuid, includes); traverseGrantsTo(graph, subjectUuid, includes);
} }
@ -96,7 +98,7 @@ public class RbacGrantsDiagramService {
.setParameter("targetObject", targetObject) .setParameter("targetObject", targetObject)
.setParameter("op", op) .setParameter("op", op)
.getSingleResult(); .getSingleResult();
final var graph = new HashSet<RawRbacGrantEntity>(); final var graph = new LimitedHashSet<RawRbacGrantEntity>();
traverseGrantsFrom(graph, refUuid, includes); traverseGrantsFrom(graph, refUuid, includes);
return toMermaidFlowchart(graph, includes); return toMermaidFlowchart(graph, includes);
} }
@ -143,6 +145,7 @@ public class RbacGrantsDiagramService {
final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n"; final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n";
return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "") return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "")
+ (grants.length() > GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "")
+ "flowchart TB\n\n" + "flowchart TB\n\n"
+ entities + entities
+ grants; + grants;
@ -208,6 +211,20 @@ public class RbacGrantsDiagramService {
return idName.replace(" ", ":").replaceAll("@.*", "") return idName.replace(" ", ":").replaceAll("@.*", "")
.replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", ""); .replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", "");
} }
class LimitedHashSet<T> extends HashSet<T> {
@Override
public boolean add(final T t) {
if (size() < GRANT_LIMIT ) {
return super.add(t);
} else {
return false;
}
}
}
} }
record Node(String idName, UUID uuid) { record Node(String idName, UUID uuid) {

View File

@ -53,8 +53,8 @@ begin
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid); assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid);
SELECT * SELECT *
FROM hs_office_relationship AS r FROM hs_office_bankaccount AS b
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = NEW.debitorRelUuid WHERE b.uuid = NEW.refundbankaccountuuid
INTO newRefundBankAccount; INTO newRefundBankAccount;
call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationshipAgent(newDebitorRel)); call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationshipAgent(newDebitorRel));
@ -113,103 +113,9 @@ declare
newRefundBankAccount hs_office_bankaccount; newRefundBankAccount hs_office_bankaccount;
begin begin
call enterTriggerForObjectUuid(NEW.uuid); delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid;
SELECT partnerRel.* call buildRbacSystemForHsOfficeDebitor(NEW);
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);
end; $$; end; $$;
/* /*

View File

@ -319,6 +319,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
givenDebitor, givenDebitor,
"hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin"); "hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin");
final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First")); final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First"));
final var givenNewBillingPerson = one(personRepo.findPersonByOptionalNameLike("Firby"));
final var givenNewContact = one(contactRepo.findContactByOptionalLabelLike("sixth contact")); final var givenNewContact = one(contactRepo.findContactByOptionalLabelLike("sixth contact"));
final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first"));
final String givenNewVatId = "NEW-VAT-ID"; final String givenNewVatId = "NEW-VAT-ID";
@ -331,7 +332,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
givenDebitor.setDebitorRel(HsOfficeRelationshipEntity.builder() givenDebitor.setDebitorRel(HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.ACCOUNTING) .relType(HsOfficeRelationshipType.ACCOUNTING)
.relAnchor(givenNewPartnerPerson) .relAnchor(givenNewPartnerPerson)
.relHolder(givenNewPartnerPerson) .relHolder(givenNewBillingPerson)
.contact(givenNewContact) .contact(givenNewContact)
.build()); .build());
givenDebitor.setRefundBankAccount(givenNewBankAccount); givenDebitor.setRefundBankAccount(givenNewBankAccount);
@ -353,7 +354,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
"hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin"); "hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_relationship#FirstGmbH-with-ACCOUNTING-FirstGmbH.agent"); "hs_office_relationship#FirstGmbH-with-ACCOUNTING-FirbySusan.agent");
// ... contact role was reassigned: // ... contact role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
@ -366,10 +367,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// ... bank-account role was reassigned: // ... bank-account role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_bankaccount#FourtheG.admin"); "hs_office_bankaccount#DE02200505501015871393.admin");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_bankaccount#FirstGmbH.admin"); "hs_office_bankaccount#DE02120300000000202051.admin");
} }
@Test @Test
@ -379,7 +380,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null, "fig"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null, "fig");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#10004:FourtheG-fourthcontact.admin"); "hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin");
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor);
final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first"));
@ -399,7 +400,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// ... bank-account role was assigned: // ... bank-account role was assigned:
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_bankaccount#FirstGmbH.admin"); "hs_office_bankaccount#DE02120300000000202051.admin");
} }
@Test @Test
@ -409,7 +410,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fih"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fih");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#10004:FourtheG-fourthcontact.admin"); "hs_office_relationship#HostsharingeG-with-PARTNER-FourtheG.agent");
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor);
// when // when
@ -428,33 +429,29 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// ... bank-account role was removed from previous bank-account admin: // ... bank-account role was removed from previous bank-account admin:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_bankaccount#FourtheG.admin"); "hs_office_bankaccount#DE02200505501015871393.admin");
} }
@Test @Test
public void partnerAdmin_canNotUpdateRelatedDebitor() { public void partnerAgent_canNotUpdateRelatedDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth", "eig"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth", "eig");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_relationship#HostsharingeG-with-PARTNER-FourtheG.admin"); "hs_office_relationship#HostsharingeG-with-PARTNER-FourtheG.agent");
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor);
// when // when
final var result = jpaAttempt.transacted(() -> { 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"); givenDebitor.setVatId("NEW-VAT-ID");
return toCleanup(debitorRepo.save(givenDebitor)); return toCleanup(debitorRepo.save(givenDebitor));
}); });
// then // then
// FIXME: This error message would be better: result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
// result.assertExceptionWithRootCauseMessage(JpaSystemException.class, "[403] Subject ", " is not allowed to update hs_office_debitor uuid");
// "[403] Subject ", " is not allowed to update hs_office_debitor uuid");
result.assertExceptionWithRootCauseMessage(
JpaObjectRetrievalFailureException.class,
"Unable to find net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity with id ");
} }
@Test @Test
@ -489,7 +486,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
found.ifPresent(foundEntity -> { found.ifPresent(foundEntity -> {
em.refresh(foundEntity); em.refresh(foundEntity);
assertThat(foundEntity).isNotSameAs(saved); 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) assertThat(foundEntity.getDebitorRel()).extracting(HsOfficeRelationshipEntity::toString)
.isEqualTo(saved.getDebitorRel().toString()); .isEqualTo(saved.getDebitorRel().toString());
}); });