From 6b09ad00033713405cdb67cf5d32c185969c2147 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 7 Jan 2025 09:30:25 +0100 Subject: [PATCH 1/2] bugfix/only-one-partner-per-person (#143) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/143 Reviewed-by: Marc Sandlus --- bin/cas-curl | 14 +++++++---- .../hsadminng/config/CasAuthenticator.java | 4 ++-- .../office/debitor/HsOfficeDebitorEntity.java | 4 ++-- .../hs/office/person/HsOfficePerson.java | 3 ++- .../hs/office/relation/HsOfficeRelation.java | 3 ++- .../hsadminng/rbac/role/WithRoleId.java | 20 ++++++++++++++++ .../db/changelog/1-rbac/1050-rbac-base.sql | 4 ++-- .../503-relation/5030-hs-office-relation.sql | 20 ++++++++++++++-- .../5038-hs-office-relation-test-data.sql | 4 ++-- .../hsadminng/hs/migration/CsvDataImport.java | 23 +++++++++---------- ...OfficeDebitorControllerAcceptanceTest.java | 16 ++++++------- ...fficeDebitorRepositoryIntegrationTest.java | 18 ++++++++------- ...OfficePartnerControllerAcceptanceTest.java | 14 +++++------ ...fficePartnerRepositoryIntegrationTest.java | 7 +++--- 14 files changed, 100 insertions(+), 54 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/rbac/role/WithRoleId.java diff --git a/bin/cas-curl b/bin/cas-curl index 45fa22dd..0e4419c4 100755 --- a/bin/cas-curl +++ b/bin/cas-curl @@ -13,10 +13,16 @@ if [ "$1" == "--trace" ]; then } function doCurl() { set -x - curl --fail-with-body \ - --header "Authorization: $HSADMINNG_CAS_TICKET" \ - --header "assumed-roles: $HSADMINNG_CAS_ASSUME" \ - "$@" + if [ -z "$HSADMINNG_CAS_ASSUME" ]; then + curl --fail-with-body \ + --header "Authorization: $HSADMINNG_CAS_TICKET" \ + "$@" + else + curl --fail-with-body \ + --header "Authorization: $HSADMINNG_CAS_TICKET" \ + --header "assumed-roles: $HSADMINNG_CAS_ASSUME" \ + "$@" + fi set +x } shift diff --git a/src/main/java/net/hostsharing/hsadminng/config/CasAuthenticator.java b/src/main/java/net/hostsharing/hsadminng/config/CasAuthenticator.java index e0d8a9f1..5d6dd116 100644 --- a/src/main/java/net/hostsharing/hsadminng/config/CasAuthenticator.java +++ b/src/main/java/net/hostsharing/hsadminng/config/CasAuthenticator.java @@ -45,13 +45,13 @@ public class CasAuthenticator implements Authenticator { private String casValidation(final HttpServletRequest httpRequest) throws SAXException, IOException, ParserConfigurationException { - System.err.println("CasAuthenticator.casValidation using CAS-server: " + casServerUrl); - final var ticket = httpRequest.getHeader("Authorization"); final var url = casServerUrl + "/p3/serviceValidate" + "?service=" + serviceUrl + "&ticket=" + ticket; + System.err.println("CasAuthenticator.casValidation using URL: " + url); + final var response = restTemplate.getForObject(url, String.class); final var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() 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 44a37754..6181c6f4 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 @@ -88,9 +88,9 @@ public class HsOfficeDebitorEntity implements BaseEntity, ( SELECT DISTINCT partner.uuid FROM hs_office.partner_rv partner - JOIN hs_office.relation_rv dRel + JOIN hs_office.relation dRel ON dRel.uuid = debitorreluuid AND dRel.type = 'DEBITOR' - JOIN hs_office.relation_rv pRel + JOIN hs_office.relation pRel ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER' WHERE pRel.holderUuid = dRel.anchorUuid ) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePerson.java b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePerson.java index 8842dac2..9b130566 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePerson.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePerson.java @@ -9,6 +9,7 @@ import lombok.experimental.FieldNameConstants; import lombok.experimental.SuperBuilder; import net.hostsharing.hsadminng.errors.DisplayAs; import net.hostsharing.hsadminng.persistence.BaseEntity; +import net.hostsharing.hsadminng.rbac.role.WithRoleId; import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.repr.Stringifyable; import org.apache.commons.lang3.StringUtils; @@ -30,7 +31,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify; @SuperBuilder(toBuilder = true) @FieldNameConstants @DisplayAs("Person") -public class HsOfficePerson & BaseEntity> implements BaseEntity, Stringifyable { +public class HsOfficePerson & BaseEntity> implements BaseEntity, Stringifyable, WithRoleId { private static Stringify toString = stringify(HsOfficePerson.class, "person") .withProp(Fields.personType, HsOfficePerson::getPersonType) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelation.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelation.java index 7a687a3b..47e19ce6 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelation.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelation.java @@ -6,6 +6,7 @@ import lombok.experimental.SuperBuilder; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity; import net.hostsharing.hsadminng.persistence.BaseEntity; +import net.hostsharing.hsadminng.rbac.role.WithRoleId; import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.repr.Stringifyable; @@ -22,7 +23,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify; @Setter @SuperBuilder(toBuilder = true) @FieldNameConstants -public class HsOfficeRelation implements BaseEntity, Stringifyable { +public class HsOfficeRelation implements BaseEntity, Stringifyable, WithRoleId { private static Stringify toString = stringify(HsOfficeRelation.class, "rel") .withProp(Fields.anchor, HsOfficeRelation::getAnchor) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/role/WithRoleId.java b/src/main/java/net/hostsharing/hsadminng/rbac/role/WithRoleId.java new file mode 100644 index 00000000..b703cc17 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/rbac/role/WithRoleId.java @@ -0,0 +1,20 @@ +package net.hostsharing.hsadminng.rbac.role; + +import jakarta.persistence.Table; +import java.util.UUID; + +public interface WithRoleId { + UUID getUuid(); + + /** + * @return the RBAC-Role-Id of the given `rbacRoleType` for this entity instance. + */ + default String roleId(final RbacRoleType rbacRoleType) { + if ( getUuid() == null ) { + throw new IllegalStateException("UUID missing => role can't be determined"); + } + final Table tableAnnot = getClass().getAnnotation(Table.class); + final var qualifiedTableName = tableAnnot.schema() + "." + tableAnnot.name(); + return qualifiedTableName + "#" + getUuid() + ":" + rbacRoleType.name(); + } +} diff --git a/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql b/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql index d2a5c7cb..bdcd9368 100644 --- a/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql +++ b/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql @@ -251,7 +251,7 @@ begin execute sql into uuid; exception when others then - raise exception 'function %_uuid_by_id_name(''%'') failed: %, SQLSTATE: %. If it could not be found, add identity view support to %\nSQL:%', + raise exception 'function %_uuid_by_id_name(''%'') failed: %, SQLSTATE: %. If the function itself could not be found, add identity view support to %\nSQL:%', objectTable, objectIdName, SQLERRM, SQLSTATE, objectTable, sql; end; if uuid is null then @@ -275,7 +275,7 @@ begin execute sql into idName; exception when others then - raise exception 'function %_id_name_by_uuid(''%'') failed: %, SQLSTATE: %. If it could not be found, add identity view support to %', + raise exception 'function %_id_name_by_uuid(''%'') failed: %, SQLSTATE: %. If the function itself could not be found, add identity view support to %', objectTable, objectUuid, SQLERRM, SQLSTATE, objectTable; end; return idName; diff --git a/src/main/resources/db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql b/src/main/resources/db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql index b470c295..813c6565 100644 --- a/src/main/resources/db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql +++ b/src/main/resources/db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql @@ -29,8 +29,24 @@ create table if not exists hs_office.relation ); --// --- TODO.impl: unique constraint, to prevent using the same person multiple times as a partner, or better: --- ( anchorUuid, holderUuid, type) + +-- ============================================================================ +--changeset michael.hoennig:hs-office-relation-unique-constraints endDelimiter:--// +-- ---------------------------------------------------------------------------- + +CREATE UNIQUE INDEX unique_relation_with_mark + ON hs_office.relation (type, anchorUuid, holderUuid, contactUuid, mark) + WHERE mark IS NOT NULL; + +CREATE UNIQUE INDEX unique_relation_without_mark + ON hs_office.relation (type, anchorUuid, holderUuid, contactUuid) + WHERE mark IS NULL; + +CREATE UNIQUE INDEX unique_partner_relation + ON hs_office.relation (type, anchorUuid, holderUuid) + WHERE mark IS NULL AND type = 'PARTNER'; + +--// -- ============================================================================ diff --git a/src/main/resources/db/changelog/5-hs-office/503-relation/5038-hs-office-relation-test-data.sql b/src/main/resources/db/changelog/5-hs-office/503-relation/5038-hs-office-relation-test-data.sql index 1e88bbe0..6673d572 100644 --- a/src/main/resources/db/changelog/5-hs-office/503-relation/5038-hs-office-relation-test-data.sql +++ b/src/main/resources/db/changelog/5-hs-office/503-relation/5038-hs-office-relation-test-data.sql @@ -100,8 +100,8 @@ do language plpgsql $$ call hs_office.relation_create_test_data('Third OHG', 'DEBITOR', 'Third OHG', 'third contact'); call hs_office.relation_create_test_data('Fourth eG', 'PARTNER', 'Hostsharing eG', 'fourth contact'); - call hs_office.relation_create_test_data('Fouler', 'REPRESENTATIVE', 'Third OHG', 'third contact'); - call hs_office.relation_create_test_data('Third OHG', 'DEBITOR', 'Third OHG', 'third contact'); + call hs_office.relation_create_test_data('Fouler', 'REPRESENTATIVE', 'Fourth eG', 'fourth contact'); + call hs_office.relation_create_test_data('Fourth eG', 'DEBITOR', 'Fourth eG', 'fourth contact'); call hs_office.relation_create_test_data('Smith', 'PARTNER', 'Hostsharing eG', 'sixth contact'); call hs_office.relation_create_test_data('Smith', 'DEBITOR', 'Smith', 'third contact'); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java index 2006a470..049681e4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java @@ -170,19 +170,18 @@ public class CsvDataImport extends ContextBasedTest { } public T persistViaEM(final Integer id, final T entity) { - //System.out.println("persisting #" + entity.hashCode() + ": " + entity); - if (!em.contains(entity)) { - em.persist(entity); + if (em.contains(entity)) { + return entity; + } + try { + em.persist(entity); + em.flush(); // makes it a bit slower, but produces better error messages + System.out.println("persisted #" + id + " as " + entity.getUuid()); + return entity; + } catch (final Exception exc) { + System.err.println("persist failed for #" + id + " as " + entity); + throw exc; // for breakpoints } - // uncomment for debugging purposes - // try { - // em.flush(); // makes it slow, but produces better error messages - // System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid()); - // return entity; - // } catch (final Exception exc) { - // throw exc; // for breakpoints - // } - return entity; } @SneakyThrows diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index 81d40a0d..7d72d56b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -616,7 +616,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu context.define("superuser-alex@hostsharing.net"); final var givenDebitor = givenSomeTemporaryDebitor(); - final var givenContact = contactRealRepo.findContactByOptionalCaptionLike("fourth").get(0); + final var givenContact = contactRealRepo.findContactByOptionalCaptionLike("tenth").get(0); final var location = RestAssured // @formatter:off .given() @@ -644,7 +644,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu "holder": { "tradeName": "Fourth eG" }, "type": "DEBITOR", "mark": null, - "contact": { "caption": "fourth contact" } + "contact": { "caption": "tenth contact" } }, "debitorNumber": "D-10004${debitorNumberSuffix}", "debitorNumberSuffix": "${debitorNumberSuffix}", @@ -685,7 +685,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu .matches(debitor -> { assertThat(debitor.getDebitorRel().getHolder().getTradeName()) .isEqualTo(givenDebitor.getDebitorRel().getHolder().getTradeName()); - assertThat(debitor.getDebitorRel().getContact().getCaption()).isEqualTo("fourth contact"); + assertThat(debitor.getDebitorRel().getContact().getCaption()).isEqualTo("tenth contact"); assertThat(debitor.getVatId()).isEqualTo("VAT222222"); assertThat(debitor.getVatCountryCode()).isEqualTo("AA"); assertThat(debitor.isVatBusiness()).isEqualTo(true); @@ -704,7 +704,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu RestAssured // @formatter:off .given() .header("current-subject", "superuser-alex@hostsharing.net") - .header("assumed-roles", "hs_office.contact#fourthcontact:ADMIN") + .header("assumed-roles", "hs_office.contact#tenthcontact:ADMIN") .contentType(ContentType.JSON) .body(""" { @@ -747,11 +747,11 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu void contactAdminUser_canNotDeleteRelatedDebitor() { context.define("superuser-alex@hostsharing.net"); final var givenDebitor = givenSomeTemporaryDebitor(); - assertThat(givenDebitor.getDebitorRel().getContact().getCaption()).isEqualTo("fourth contact"); + assertThat(givenDebitor.getDebitorRel().getContact().getCaption()).isEqualTo("tenth contact"); RestAssured // @formatter:off .given() - .header("current-subject", "contact-admin@fourthcontact.example.com") + .header("current-subject", "contact-admin@tenthcontact.example.com") .port(port) .when() .delete("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid()) @@ -766,7 +766,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu void normalUser_canNotDeleteUnrelatedDebitor() { context.define("superuser-alex@hostsharing.net"); final var givenDebitor = givenSomeTemporaryDebitor(); - assertThat(givenDebitor.getDebitorRel().getContact().getCaption()).isEqualTo("fourth contact"); + assertThat(givenDebitor.getDebitorRel().getContact().getCaption()).isEqualTo("tenth contact"); RestAssured // @formatter:off .given() @@ -786,7 +786,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0).load(); - final var givenContact = contactRealRepo.findContactByOptionalCaptionLike("fourth contact").get(0); + final var givenContact = contactRealRepo.findContactByOptionalCaptionLike("tenth contact").get(0); final var newDebitor = HsOfficeDebitorEntity.builder() .debitorNumberSuffix(nextDebitorSuffix()) .billable(true) 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 27bc08bc..948ec958 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 @@ -33,6 +33,7 @@ import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; +import static net.hostsharing.hsadminng.rbac.role.RbacRoleType.ADMIN; import static net.hostsharing.hsadminng.rbac.test.EntityList.one; import static net.hostsharing.hsadminng.rbac.grant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.role.RawRbacRoleEntity.distinctRoleNamesOf; @@ -85,7 +86,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean final var count = debitorRepo.count(); final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); - final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("first contact")); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("eleventh contact")); // when final var result = attempt(em, () -> { @@ -119,7 +120,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // given context("superuser-alex@hostsharing.net"); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); - final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("first contact")); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("eleventh contact")); // when final var result = attempt(em, () -> { @@ -335,10 +336,11 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // given context("superuser-alex@hostsharing.net"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif"); + final var originalDebitorRel = givenDebitor.getDebitorRel(); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office.relation#FourtheG-with-DEBITOR-FourtheG:ADMIN", true); + givenDebitor.getDebitorRel().roleId(ADMIN), true); final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First")); final var givenNewBillingPerson = one(personRepo.findPersonByOptionalNameLike("Firby")); final var givenNewContact = one(contactRealRepo.findContactByOptionalCaptionLike("sixth contact")); @@ -371,10 +373,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // ... partner role was reassigned: assertThatDebitorIsNotVisibleForUserWithRole( result.returnedValue(), - "hs_office.relation#FourtheG-with-DEBITOR-FourtheG:ADMIN"); + originalDebitorRel.roleId(ADMIN)); assertThatDebitorIsVisibleForUserWithRole( result.returnedValue(), - "hs_office.relation#FirstGmbH-with-DEBITOR-FirbySusan:AGENT", true); + result.returnedValue().getDebitorRel().roleId(ADMIN), true); // ... contact role was reassigned: assertThatDebitorIsNotVisibleForUserWithRole( @@ -397,10 +399,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean public void globalAdmin_canUpdateNullRefundBankAccountToNotNullBankAccountForArbitraryDebitor() { // given context("superuser-alex@hostsharing.net"); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null, "fig"); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "tenth contact", null, "fig"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office.relation#FourtheG-with-DEBITOR-FourtheG:ADMIN", true); + givenDebitor.getDebitorRel().roleId(ADMIN), true); assertThatDebitorActuallyInDatabase(givenDebitor, true); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); @@ -564,7 +566,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office.relation#FourtheG-with-DEBITOR-FourtheG:ADMIN"); + context("superuser-alex@hostsharing.net", givenDebitor.getDebitorRel().roleId(ADMIN)); assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent(); debitorRepo.deleteByUuid(givenDebitor.getUuid()); 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 dc66c74b..54a4baf2 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 @@ -94,7 +94,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu context.define("superuser-alex@hostsharing.net"); final var givenMandantPerson = personRealRepo.findPersonByOptionalNameLike("Hostsharing eG").stream().findFirst().orElseThrow(); - final var givenPerson = personRealRepo.findPersonByOptionalNameLike("Third").stream().findFirst().orElseThrow(); + final var givenPerson = personRealRepo.findPersonByOptionalNameLike("Winkler").stream().findFirst().orElseThrow(); final var givenContact = contactRealRepo.findContactByOptionalCaptionLike("fourth").stream().findFirst().orElseThrow(); final var location = RestAssured // @formatter:off @@ -129,7 +129,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu "partnerNumber": "P-20002", "partnerRel": { "anchor": { "tradeName": "Hostsharing eG" }, - "holder": { "tradeName": "Third OHG" }, + "holder": { "familyName": "Winkler" }, "type": "PARTNER", "mark": null, "contact": { "caption": "fourth contact" } @@ -315,7 +315,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu context.define("superuser-alex@hostsharing.net"); final var givenPartner = givenSomeTemporaryPartnerBessler(20011); - final var givenPartnerRel = givenSomeTemporaryPartnerRel("Third OHG", "third contact"); + final var givenPartnerRel = givenSomeTemporaryPartnerRel("Winkler", "third contact"); RestAssured // @formatter:off .given() @@ -345,7 +345,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu "partnerNumber": "P-20011", "partnerRel": { "anchor": { "tradeName": "Hostsharing eG" }, - "holder": { "tradeName": "Third OHG" }, + "holder": { "familyName": "Winkler" }, "type": "PARTNER", "contact": { "caption": "third contact" } }, @@ -366,7 +366,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu assertThat(partnerRepo.findByUuid(givenPartner.getUuid())).isPresent().get() .matches(partner -> { assertThat(partner.getPartnerNumber()).isEqualTo(givenPartner.getPartnerNumber()); - assertThat(partner.getPartnerRel().getHolder().getTradeName()).isEqualTo("Third OHG"); + assertThat(partner.getPartnerRel().getHolder().getFamilyName()).isEqualTo("Winkler"); assertThat(partner.getPartnerRel().getContact().getCaption()).isEqualTo("third contact"); assertThat(partner.getDetails().getRegistrationOffice()).isEqualTo("Temp Registergericht Aurich"); assertThat(partner.getDetails().getRegistrationNumber()).isEqualTo("222222"); @@ -382,7 +382,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu context.define("superuser-alex@hostsharing.net"); final var givenPartner = givenSomeTemporaryPartnerBessler(20011); - final var givenPartnerRel = givenSomeTemporaryPartnerRel("Third OHG", "third contact"); + final var givenPartnerRel = givenSomeTemporaryPartnerRel("Winkler", "third contact"); RestAssured // @formatter:off .given() @@ -404,7 +404,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu context.define("superuser-alex@hostsharing.net"); assertThat(partnerRepo.findByUuid(givenPartner.getUuid())).isPresent().get() .matches(partner -> { - assertThat(partner.getPartnerRel().getHolder().getTradeName()).isEqualTo("Third OHG"); + assertThat(partner.getPartnerRel().getHolder().getFamilyName()).isEqualTo("Winkler"); assertThat(partner.getPartnerRel().getContact().getCaption()).isEqualTo("third contact"); return true; }); 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 f2d09c12..c8a709a1 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 @@ -31,6 +31,7 @@ import static net.hostsharing.hsadminng.rbac.grant.RawRbacGrantEntity.distinctGr import static net.hostsharing.hsadminng.rbac.role.RawRbacObjectEntity.objectDisplaysOf; import static net.hostsharing.hsadminng.rbac.role.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.mapper.Array.from; +import static net.hostsharing.hsadminng.rbac.role.RbacRoleType.ADMIN; import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt; import static org.assertj.core.api.Assertions.assertThat; @@ -76,7 +77,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean // given context("superuser-alex@hostsharing.net"); final var count = partnerRepo.count(); - final var partnerRel = givenSomeTemporaryHostsharingPartnerRel("First GmbH", "first contact"); + final var partnerRel = givenSomeTemporaryHostsharingPartnerRel("Winkler", "first contact"); // when final var result = attempt(em, () -> { @@ -269,7 +270,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - givenPartner.setPartnerRel(givenSomeTemporaryHostsharingPartnerRel("Third OHG", "sixth contact")); + givenPartner.setPartnerRel(givenSomeTemporaryHostsharingPartnerRel("Winkler", "sixth contact")); return partnerRepo.save(givenPartner); }); @@ -281,7 +282,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean "rbac.global#global:ADMIN"); assertThatPartnerIsVisibleForUserWithRole( givenPartner, - "hs_office.person#ThirdOHG:ADMIN"); + givenPartner.getPartnerRel().getHolder().roleId(ADMIN)); assertThatPartnerIsNotVisibleForUserWithRole( givenPartner, "hs_office.person#ErbenBesslerMelBessler:ADMIN"); From 329122347d1ee2d324f2e22417282e13f79af631 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 8 Jan 2025 12:34:18 +0100 Subject: [PATCH 2/2] Swagger-UI on actuator-port to bypass CAS, also enables /actuator/mappings endpoint --- README.md | 2 +- src/main/resources/application.yml | 6 +++++- .../hsadminng/config/WebSecurityConfigIntegrationTest.java | 4 ++-- src/test/resources/application.yml | 4 ++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cfa7f45f..a015f73c 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Also try for example 'admin@xxx.example.com' or 'unknown@example.org'. If you want a formatted JSON output, you can pipe the result to `jq` or similar. -And to see the full, currently implemented, API, open http://localhost:8080/swagger-ui/index.html. +And to see the full, currently implemented, API, open http://localhost:8081/actuator/swagger-ui/index.html (uses management-port and thus bypasses authentication). If you still need to install some of these tools, find some hints in the next chapters. diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 69ad1e1b..f6a6fe88 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -9,7 +9,7 @@ management: web: exposure: # HOWTO: view _clickable_ Spring Actuator (Micrometer) Metrics endpoints: http://localhost:8081/actuator/metric-links - include: info, health, metrics, metric-links + include: info, health, metrics, metric-links, mappings, openapi, swaggerui observations: annotations: enabled: true @@ -30,6 +30,10 @@ spring: hibernate: dialect: net.hostsharing.hsadminng.config.PostgresCustomDialect +# keep this in sync with test/.../application.yml +springdoc: + use-management-port: true + liquibase: contexts: dev diff --git a/src/test/java/net/hostsharing/hsadminng/config/WebSecurityConfigIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/config/WebSecurityConfigIntegrationTest.java index 586702c2..00444c3a 100644 --- a/src/test/java/net/hostsharing/hsadminng/config/WebSecurityConfigIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/config/WebSecurityConfigIntegrationTest.java @@ -82,14 +82,14 @@ class WebSecurityConfigIntegrationTest { @Test public void shouldSupportSwaggerUi() { final var result = this.restTemplate.getForEntity( - "http://localhost:" + this.managementPort + "/swagger-ui/index.html", String.class); + "http://localhost:" + this.managementPort + "/actuator/swagger-ui/index.html", String.class); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); } @Test public void shouldSupportApiDocs() { final var result = this.restTemplate.getForEntity( - "http://localhost:" + this.managementPort + "/v3/api-docs/swagger-config", String.class); + "http://localhost:" + this.managementPort + "/actuator/v3/api-docs/swagger-config", String.class); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); // permitted but not configured } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index a69f8aa1..954bdd63 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -39,6 +39,10 @@ spring: change-log: classpath:/db/changelog/db.changelog-master.yaml contexts: tc,test,dev,pg_stat_statements +# keep this in sync with main/.../application.yml +springdoc: + use-management-port: true + logging: level: liquibase: WARN