diff --git a/src/main/java/net/hostsharing/hsadminng/context/Context.java b/src/main/java/net/hostsharing/hsadminng/context/Context.java index 6ceed023..c3b27126 100644 --- a/src/main/java/net/hostsharing/hsadminng/context/Context.java +++ b/src/main/java/net/hostsharing/hsadminng/context/Context.java @@ -58,7 +58,7 @@ public class Context { cast(:currentTask as varchar(127)), cast(:currentRequest as text), cast(:currentSubject as varchar(63)), - cast(:assumedRoles as varchar(1023))); + cast(:assumedRoles as text)); """); query.setParameter("currentTask", shortenToMaxLength(currentTask, 127)); query.setParameter("currentRequest", currentRequest); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRbacRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRbacRepository.java index 82e245ae..e687bdcb 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRbacRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRbacRepository.java @@ -42,7 +42,7 @@ public interface HsOfficeRelationRbacRepository extends Repository findByUuid(UUID id); - default List findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) { - return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType == null ? null : relationType.toString()); - } - @Query(value = """ SELECT p.* FROM hs_office.relation AS p WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid - """, nativeQuery = true) + """, nativeQuery = true) @Timed("app.repo.relations.findRelationRelatedToPersonUuid.real") List findRelationRelatedToPersonUuid(@NotNull UUID personUuid); + /** + * Finds relations by a conjunction of optional criteria, including anchorPerson, holderPerson and contact data. + * * + * @param personUuid the optional UUID of the anchorPerson or holderPerson + * @param relationType the type of the relation + * @param mark the mark (use '%' for wildcard), case ignored + * @param personData a string to match the persons tradeName, familyName or givenName (use '%' for wildcard), case ignored + * @param contactData a string to match the contacts caption, postalAddress, emailAddresses or phoneNumbers (use '%' for wildcard), case ignored + * @return a list of (accessible) relations which match all given criteria + */ + default List findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData( + final UUID personUuid, + final HsOfficeRelationType relationType, + final String mark, + final String personData, + final String contactData) { + return findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl( + personUuid, toStringOrNull(relationType), + toSqlLikeOperand(mark), toSqlLikeOperand(personData), toSqlLikeOperand(contactData)); + } + + // TODO: Or use jsonb_path with RegEx like emailAddressRegEx in ContactRepo? @Query(value = """ - SELECT p.* FROM hs_office.relation AS p - WHERE (:relationType IS NULL OR p.type = cast(:relationType AS hs_office.RelationType)) - AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid) - """, nativeQuery = true) - @Timed("app.repo.relations.findRelationRelatedToPersonUuidAndRelationTypeString.real") - List findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType); + SELECT rel FROM HsOfficeRelationRealEntity AS rel + WHERE (:relationType IS NULL OR CAST(rel.type AS String) = :relationType) + AND ( :personUuid IS NULL + OR rel.anchor.uuid = :personUuid OR rel.holder.uuid = :personUuid ) + AND ( :mark IS NULL OR lower(rel.mark) LIKE :mark ) + AND ( :personData IS NULL + OR lower(rel.anchor.tradeName) LIKE :personData OR lower(rel.holder.tradeName) LIKE :personData + OR lower(rel.anchor.familyName) LIKE :personData OR lower(rel.holder.familyName) LIKE :personData + OR lower(rel.anchor.givenName) LIKE :personData OR lower(rel.holder.givenName) LIKE :personData ) + AND ( :contactData IS NULL + OR lower(rel.contact.caption) LIKE :contactData + OR lower(CAST(rel.contact.postalAddress AS String)) LIKE :contactData + OR lower(CAST(rel.contact.emailAddresses AS String)) LIKE :contactData + OR lower(CAST(rel.contact.phoneNumbers AS String)) LIKE :contactData ) + """) + @Timed("app.office.relations.repo.findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl.real") + List findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl( + final UUID personUuid, + final String relationType, + final String mark, + final String personData, + final String contactData); @Timed("app.repo.relations.save.real") HsOfficeRelationRealEntity save(final HsOfficeRelationRealEntity entity); @@ -41,4 +75,11 @@ public interface HsOfficeRelationRealRepository extends Repository p.getPersonType() == UNINCORPORATED_FIRM) @@ -71,20 +100,21 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Paul").stream() .filter(p -> p.getPersonType() == NATURAL_PERSON) .findFirst().orElseThrow(); - final var givenContact = contactrealRepo.findContactByOptionalCaptionLike("fourth contact").stream() + final var givenContact = contactRealRepo.findContactByOptionalCaptionLike("fourth contact").stream() .findFirst().orElseThrow(); // when - final var result = attempt(em, () -> { - final var newRelation = HsOfficeRelationRbacEntity.builder() - .anchor(givenAnchorPerson) - .holder(givenHolderPerson) - .type(HsOfficeRelationType.SUBSCRIBER) - .mark("operations-announce") - .contact(givenContact) - .build(); - return toCleanup(relationRbacRepo.save(newRelation)); - }); + final var result = attempt( + em, () -> { + final var newRelation = HsOfficeRelationRbacEntity.builder() + .anchor(givenAnchorPerson) + .holder(givenHolderPerson) + .type(HsOfficeRelationType.SUBSCRIBER) + .mark("operations-announce") + .contact(givenContact) + .build(); + return toCleanup(relationRbacRepo.save(newRelation)); + }); // then result.assertSuccessful(); @@ -93,7 +123,8 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea assertThat(relationRbacRepo.count()).isEqualTo(count + 1); final var stored = relationRbacRepo.findByUuid(result.returnedValue().getUuid()); assertThat(stored).isNotEmpty().map(HsOfficeRelation::toString).get() - .isEqualTo("rel(anchor='UF Erben Bessler', type='SUBSCRIBER', mark='operations-announce', holder='NP Winkler, Paul', contact='fourth contact')"); + .isEqualTo( + "rel(anchor='UF Erben Bessler', type='SUBSCRIBER', mark='operations-announce', holder='NP Winkler, Paul', contact='fourth contact')"); } @Test @@ -104,23 +135,24 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()); // when - attempt(em, () -> { - final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Bessler").stream() - .filter(p -> p.getPersonType() == UNINCORPORATED_FIRM) - .findFirst().orElseThrow(); - final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Bert").stream() - .filter(p -> p.getPersonType() == NATURAL_PERSON) - .findFirst().orElseThrow(); - final var givenContact = contactrealRepo.findContactByOptionalCaptionLike("fourth contact").stream() - .findFirst().orElseThrow(); - final var newRelation = HsOfficeRelationRbacEntity.builder() - .anchor(givenAnchorPerson) - .holder(givenHolderPerson) - .type(HsOfficeRelationType.REPRESENTATIVE) - .contact(givenContact) - .build(); - return toCleanup(relationRbacRepo.save(newRelation)); - }); + attempt( + em, () -> { + final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Bessler").stream() + .filter(p -> p.getPersonType() == UNINCORPORATED_FIRM) + .findFirst().orElseThrow(); + final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Bert").stream() + .filter(p -> p.getPersonType() == NATURAL_PERSON) + .findFirst().orElseThrow(); + final var givenContact = contactRealRepo.findContactByOptionalCaptionLike("fourth contact").stream() + .findFirst().orElseThrow(); + final var newRelation = HsOfficeRelationRbacEntity.builder() + .anchor(givenAnchorPerson) + .holder(givenHolderPerson) + .type(HsOfficeRelationType.REPRESENTATIVE) + .contact(givenContact) + .build(); + return toCleanup(relationRbacRepo.save(newRelation)); + }); // then assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( @@ -180,7 +212,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea allTheseRelationsAreReturned( result, "rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Smith, Peter', contact='sixth contact')", - "rel(anchor='LP Second e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')", + "rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')", "rel(anchor='IF Third OHG', type='SUBSCRIBER', mark='members-announce', holder='NP Smith, Peter', contact='third contact')"); } @@ -193,12 +225,17 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea .findFirst().orElseThrow(); // when: - final var result = relationRbacRepo.findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData(person.getUuid(), null, null, null, null); + final var result = relationRbacRepo.findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData( + person.getUuid(), + null, + null, + null, + null); // then: exactlyTheseRelationsAreReturned( result, - "rel(anchor='LP Second e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')", + "rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')", "rel(anchor='IF Third OHG', type='SUBSCRIBER', mark='members-announce', holder='NP Smith, Peter', contact='third contact')", "rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Smith, Peter', contact='sixth contact')", "rel(anchor='NP Smith, Peter', type='DEBITOR', holder='NP Smith, Peter', contact='third contact')"); @@ -219,7 +256,10 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea givenRelation, "hs_office.person#ErbenBesslerMelBessler:ADMIN"); context("superuser-alex@hostsharing.net"); - final var givenContact = contactrealRepo.findContactByOptionalCaptionLike("sixth contact").stream().findFirst().orElseThrow(); + final var givenContact = contactRealRepo.findContactByOptionalCaptionLike("sixth contact") + .stream() + .findFirst() + .orElseThrow(); // when final var result = jpaAttempt.transacted(() -> { @@ -258,13 +298,16 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office.relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerAnita:AGENT"); + context( + "superuser-alex@hostsharing.net", + "hs_office.relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerAnita:AGENT"); givenRelation.setContact(null); return relationRbacRepo.save(givenRelation); }); // then - result.assertExceptionWithRootCauseMessage(JpaSystemException.class, + result.assertExceptionWithRootCauseMessage( + JpaSystemException.class, "[403] Subject ", " is not allowed to update hs_office.relation uuid"); } @@ -287,7 +330,8 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea }); // then - result.assertExceptionWithRootCauseMessage(JpaSystemException.class, + result.assertExceptionWithRootCauseMessage( + JpaSystemException.class, "[403] Subject ", " is not allowed to update hs_office.relation uuid"); } @@ -397,7 +441,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea select currentTask, targetTable, targetOp, targetdelta->>'mark' from base.tx_journal_v where targettable = 'hs_office.relation'; - """); + """); // when @SuppressWarnings("unchecked") final List customerLogEntries = query.getResultList(); @@ -412,7 +456,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea 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 = contactrealRepo.findContactByOptionalCaptionLike(contact).get(0); + final var givenContact = contactRealRepo.findContactByOptionalCaptionLike(contact).get(0); final var newRelation = HsOfficeRelationRbacEntity.builder() .type(HsOfficeRelationType.REPRESENTATIVE) .anchor(givenAnchorPerson) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java index ef334b0a..eabde5a7 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java @@ -83,7 +83,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl }, { "debitor": { "debitorNumber": "D-1000212" }, - "bankAccount": { "holder": "Second e.K." }, + "bankAccount": { "holder": "Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K." }, "reference": "ref-10002-12", "validFrom": "2022-10-01", "validTo": "2026-12-31" diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleRepositoryIntegrationTest.java index 7540777a..98495e81 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleRepositoryIntegrationTest.java @@ -68,7 +68,7 @@ class RbacRoleRepositoryIntegrationTest { } @Test - public void globalAdmin_withAssumedglobalAdminRole_canViewAllRbacRoles() { + public void globalAdmin_withAssumedGlobalAdminRole_canViewAllRbacRoles() { given: context.define("superuser-alex@hostsharing.net", "rbac.global#global:ADMIN");