From 87c7d2f531e03a79f4a368541ea89c4efeabb0dd Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 18 Dec 2024 10:49:05 +0100 Subject: [PATCH 01/31] feature/add-scenario-test-for-deceased-partner-with-community-of-heirs (#137) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/137 Reviewed-by: Marc Sandlus --- .../config/JsonObjectMapperConfiguration.java | 5 + .../scenarios/HsOfficeScenarioTests.java | 55 +++++-- .../scenarios/partner/CreatePartner.java | 7 +- ...ceDeceasedPartnerWithCommunityOfHeirs.java | 139 ++++++++++++++++++ .../hsadminng/hs/scenarios/JsonOptional.java | 10 ++ .../hsadminng/hs/scenarios/ScenarioTest.java | 52 +++---- .../hsadminng/hs/scenarios/TestReport.java | 34 ++++- .../hsadminng/hs/scenarios/UseCase.java | 117 ++++++++++++--- 8 files changed, 347 insertions(+), 72 deletions(-) create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java diff --git a/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java b/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java index d5ff80d9..87276f34 100644 --- a/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java +++ b/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java @@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.config; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.openapitools.jackson.nullable.JsonNullableModule; @@ -14,6 +15,10 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; @Configuration public class JsonObjectMapperConfiguration { + public static ObjectMapper build() { + return new JsonObjectMapperConfiguration().customObjectMapper().build(); + } + @Bean @Primary public Jackson2ObjectMapperBuilder customObjectMapper() { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index d23a4250..f3037336 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -29,6 +29,7 @@ import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContac import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddRepresentativeToPartner; import net.hostsharing.hsadminng.hs.office.scenarios.partner.CreatePartner; import net.hostsharing.hsadminng.hs.office.scenarios.partner.DeletePartner; +import net.hostsharing.hsadminng.hs.office.scenarios.partner.ReplaceDeceasedPartnerWithCommunityOfHeirs; import net.hostsharing.hsadminng.hs.office.scenarios.person.ShouldUpdatePersonData; import net.hostsharing.hsadminng.hs.office.scenarios.subscription.RemoveOperationsContactFromPartner; import net.hostsharing.hsadminng.hs.office.scenarios.subscription.SubscribeExistingPersonAndContactToMailinglist; @@ -93,6 +94,8 @@ class HsOfficeScenarioTests extends ScenarioTest { """) .given("officePhoneNumber", "+49 40 654321-0") .given("emailAddress", "hamburg@test-ag.example.org") + .given("registrationOffice", "Registergericht Hamburg") + .given("registrationNumber", "1234567") .doRun() .keep(); } @@ -118,6 +121,9 @@ class HsOfficeScenarioTests extends ScenarioTest { """) .given("officePhoneNumber", "+49 40 123456") .given("emailAddress", "michelle.matthieu@example.org") + .given("birthday", "1951-03-25") + .given("birthPlace", "Neustadt a.d.R.") + .given("birthName", "Eichbaum") .doRun() .keep(); } @@ -221,16 +227,17 @@ class HsOfficeScenarioTests extends ScenarioTest { new ReplaceContactData(scenarioTest) .given("partnerName", "Test AG") .given("newContactCaption", "Test AG - China") - .given("newPostalAddress", """ - "firm": "Test AG", - "name": "Fi Zhong-Kha", - "building": "Thi Chi Koh Building", - "street": "No.2 Commercial Second Street", - "district": "Niushan Wei Wu", - "city": "Dongguan City", - "province": "Guangdong Province", - "country": "China" - """) + .given( + "newPostalAddress", """ + "firm": "Test AG", + "name": "Fi Zhong-Kha", + "building": "Thi Chi Koh Building", + "street": "No.2 Commercial Second Street", + "district": "Niushan Wei Wu", + "city": "Dongguan City", + "province": "Guangdong Province", + "country": "China" + """) .given("newOfficePhoneNumber", "++15 999 654321") .given("newEmailAddress", "norden@test-ag.example.org") .doRun(); @@ -257,6 +264,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Order(20) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class DebitorScenarios { + @Test @Order(2010) @Requires("Partner: P-31010 - Test AG") @@ -601,4 +609,31 @@ class HsOfficeScenarioTests extends ScenarioTest { .doRun(); } } + + @Nested + @Order(60) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class PartnerDeceasedScenarios { + + @Test + @Order(6010) + @Requires("Partner: P-31011 - Michelle Matthieu") + void shouldReplaceDeceasedPartnerByCommunityOfHeirs() { + new ReplaceDeceasedPartnerWithCommunityOfHeirs(scenarioTest) + .given("partnerNumber", "P-31011") + .given("dateOfDeath", "2024-11-15") + .given("representativeGivenName", "Lena") + .given("representativeFamilyName", "Stadland") + .given( + "communityOfHeirsPostalAddress", """ + "street": "Im Wischer 14", + "zipcode": "22987", + "city": "Hamburg", + "country": "Germany" + """) + .given("communityOfHeirsOfficePhoneNumber", "+49 40 666666") + .given("communityOfHeirsEmailAddress", "lena.stadland@example.org") + .doRun(); + } + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/CreatePartner.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/CreatePartner.java index 87ca7c87..a1ddccc4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/CreatePartner.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/CreatePartner.java @@ -69,8 +69,11 @@ public class CreatePartner extends UseCase { "contact.uuid": ${Contact: %{contactCaption}} }, "details": { - "registrationOffice": "Registergericht Hamburg", - "registrationNumber": "1234567" + "birthday": ${birthday???}, + "birthPlace": ${birthPlace???}, + "birthName": ${birthName???}, + "registrationOffice": ${registrationOffice???}, + "registrationNumber": ${registrationNumber???} } } """)) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java new file mode 100644 index 00000000..c706e8e6 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -0,0 +1,139 @@ +package net.hostsharing.hsadminng.hs.office.scenarios.partner; + +import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; +import net.hostsharing.hsadminng.hs.scenarios.UseCase; +import org.springframework.http.HttpStatus; + +import static io.restassured.http.ContentType.JSON; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.OK; + +public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase { + + public ReplaceDeceasedPartnerWithCommunityOfHeirs(final ScenarioTest testSuite) { + super(testSuite); + + } + + @Override + protected HttpResponse run() { + + obtain("Person: Hostsharing eG", () -> + httpGet("/api/hs/office/persons?name=Hostsharing+eG") + .expecting(OK).expecting(JSON), + response -> response.expectArrayElements(1).getFromBody("[0].uuid"), + "Even in production data we expect this query to return just a single result." // TODO.impl: add constraint? + ); + + obtain("Partner: %{partnerNumber}", () -> + httpGet("/api/hs/office/partners/%{partnerNumber}") + .reportWithResponse().expecting(OK).expecting(JSON), + response -> response.getFromBody("uuid"), + "Even in production data we expect this query to return just a single result." // TODO.impl: add constraint? + ) + .extractValue("partnerRel.holder.familyName", "familyNameOfDeceasedPerson") + .extractValue("partnerRel.holder.givenName", "givenNameOfDeceasedPerson") + .extractUuidAlias("partnerRel.holder.uuid", "Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"); + + obtain("Partner-Relation: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () -> + httpPost("/api/hs/office/relations", usingJsonBody(""" + { + "type": "PARTNER", + "anchor.uuid": ${Person: Hostsharing eG}, + "holder": { + "personType": "UNINCORPORATED_FIRM", + "tradeName": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", + }, + "contact": { + "caption": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", + "postalAddress": { + "name": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", + "co": "%{representativeGivenName} %{representativeFamilyName}", + %{communityOfHeirsPostalAddress} + }, + "phoneNumbers": { + "office": ${communityOfHeirsOfficePhoneNumber} + }, + "emailAddresses": { + "main": ${communityOfHeirsEmailAddress} + } + } + } + """)) + .reportWithResponse().expecting(CREATED).expecting(JSON) + ) + .extractUuidAlias("contact.uuid", "Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}") + .extractUuidAlias("holder.uuid", "Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"); + + obtain("Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () -> + httpPost("/api/hs/office/relations", usingJsonBody(""" + { + "type": "REPRESENTATIVE", + "anchor.uuid": ${Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}, + "holder": { + "personType": "NATURAL_PERSON", + "givenName": ${representativeGivenName}, + "familyName": ${representativeFamilyName} + }, + "contact.uuid": ${Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}} + } + """)) + .reportWithResponse().expecting(CREATED).expecting(JSON) + ).extractUuidAlias("holder.uuid", "Person: %{representativeGivenName} %{representativeFamilyName}"); + + obtain("Partner: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () -> + httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}", usingJsonBody(""" + { + "partnerRel.uuid": ${Partner-Relation: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}} + } + """)) + .expecting(HttpStatus.OK) + ); + + // TODO.test: missing steps Debitor, Membership, Coop-Shares+Assets + + // Debitors + + // die Erbengemeinschaft wird als Anchor-Person (Partner) in die Debitor-Relations eingetragen + // der neue Rechnungsempfänger (z.B. auch ggf. Rechtsanwalt) wird als Holder-Person (Debitor-Person) in die Debitor-Relations eingetragen -- oder neu? + + // Membership + + // intro: die Mitgliedschaft geht juristisch gesehen auf die Erbengemeinschaft über + + // die bisherige Mitgliedschaft als DECEASED mit Ende-Datum=Todesdatum markieren + + // eine neue Mitgliedschaft (-00) mit dem Start-Datum=Todesdatum+1 anlegen + + // die Geschäftsanteile per share-tx: TRANSFER→ADOPT an die Erbengemeinschaft übertragen + // die Geschäftsguthaben per asset-tx: TRANSFER→ADOPT an die Erbengemeinschaft übertragen + + // outro: die Erbengemeinschaft hat eine Frist von 6 Monaten, um die Mitgliedschaft einer Person zu übertragen + // →nächster "Drecksfall" + + return null; + } + + @Override + protected void verify(final UseCase.HttpResponse response) { + verify( + "Verify the Updated Partner", + () -> httpGet("/api/hs/office/partners/%{partnerNumber}") + .expecting(OK).expecting(JSON).expectObject(), + path("partnerRel.holder.tradeName").contains("Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}") + ); + + // TODO.test: Verify the EX_PARTNER-Relation, once we fixed the anchor problem, see HsOfficePartnerController + // (net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerController.optionallyCreateExPartnerRelation) + + verify( + "Verify the Representative-Relation", + () -> httpGet("/api/hs/office/relations?relationType=REPRESENTATIVE&personUuid=%{Person: %{representativeGivenName} %{representativeFamilyName}}") + .expecting(OK).expecting(JSON).expectArrayElements(1), + path("[0].anchor.tradeName").contains("Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"), + path("[0].holder.familyName").contains("%{representativeFamilyName}") + ); + + // TODO.test: Verify Debitor, Membership, Coop-Shares and Coop-Assets once implemented + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/JsonOptional.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/JsonOptional.java index f5c829c6..6fff42df 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/JsonOptional.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/JsonOptional.java @@ -1,5 +1,7 @@ package net.hostsharing.hsadminng.hs.scenarios; +import java.util.Objects; +import java.util.UUID; public final class JsonOptional { @@ -41,4 +43,12 @@ public final class JsonOptional { } return jsonValue == null ? null : jsonValue.toString(); } + + public UUID givenUUID() { + try { + return UUID.fromString(Objects.toString(jsonValue)); + } catch(final IllegalArgumentException e) { + throw new ClassCastException("expected a UUID, but got '" + jsonValue + "'"); + } + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java index 75ea20e7..9d65fed2 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java @@ -21,7 +21,6 @@ import java.math.BigDecimal; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Stack; import java.util.UUID; @@ -50,16 +49,7 @@ public abstract class ScenarioTest extends ContextBasedTest { return Optional.of(currentTestMethodProduces.pop()); } - record Alias>(Class useCase, UUID uuid) { - - @Override - public String toString() { - return Objects.toString(uuid); - } - - } - - private final static Map> aliases = new HashMap<>(); + private final static Map aliases = new HashMap<>(); private final static Map properties = new HashMap<>(); public final TestReport testReport = new TestReport(aliases); @@ -90,18 +80,7 @@ public abstract class ScenarioTest extends ContextBasedTest { @AfterEach void afterScenario(final TestInfo testInfo) { // final TestInfo testInfo - testInfo.getTestMethod() .ifPresent(currentTestMethod -> { - // FIXME: extract to method - final var producesAnnot = currentTestMethod.getAnnotation(Produces.class); - if (producesAnnot != null && producesAnnot.permanent()) { - final var testMethodProduces = producedAliases(producesAnnot); - testMethodProduces.forEach(declaredAlias -> - assertThat(knowVariables().containsKey(declaredAlias)) - .as("@Producer method " + currentTestMethod.getName() + - " did declare but not produce \"" + declaredAlias + "\"") - .isTrue() ); - } - }); + verifyProduceDeclaration(testInfo); properties.clear(); testReport.close(); @@ -111,14 +90,11 @@ public abstract class ScenarioTest extends ContextBasedTest { jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); - aliases.put( + putAlias( "Person: Hostsharing eG", - new Alias<>( - null, - personRepo.findPersonByOptionalNameLike("Hostsharing eG") - .stream() + personRepo.findPersonByOptionalNameLike("Hostsharing eG").stream() .map(HsOfficePersonRbacEntity::getUuid) - .reduce(Reducer::toSingleElement).orElseThrow()) + .reduce(Reducer::toSingleElement).orElseThrow() ); } ); @@ -195,6 +171,20 @@ public abstract class ScenarioTest extends ContextBasedTest { } } + private static void verifyProduceDeclaration(final TestInfo testInfo) { + testInfo.getTestMethod().ifPresent(currentTestMethod -> { + final var producesAnnot = currentTestMethod.getAnnotation(Produces.class); + if (producesAnnot != null && producesAnnot.permanent()) { + final var testMethodProduces = producedAliases(producesAnnot); + testMethodProduces.forEach(declaredAlias -> + assertThat(knowVariables().containsKey(declaredAlias)) + .as("@Producer method " + currentTestMethod.getName() + + " did declare but not produce \"" + declaredAlias + "\"") + .isTrue() ); + } + }); + } + static boolean containsAlias(final String alias) { return aliases.containsKey(alias); } @@ -210,7 +200,7 @@ public abstract class ScenarioTest extends ContextBasedTest { return alias; } - static void putAlias(final String name, final Alias value) { + static void putAlias(final String name, final UUID value) { aliases.put(name, value); } @@ -224,7 +214,7 @@ public abstract class ScenarioTest extends ContextBasedTest { static Map knowVariables() { final var map = new LinkedHashMap(); - ScenarioTest.aliases.forEach((key, value) -> map.put(key, value.uuid())); + map.putAll(ScenarioTest.aliases); map.putAll(ScenarioTest.properties); return map; } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java index b700d556..028e679a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java @@ -1,6 +1,9 @@ package net.hostsharing.hsadminng.hs.scenarios; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; +import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; import net.hostsharing.hsadminng.system.SystemProcess; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Order; @@ -15,6 +18,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Map; +import java.util.UUID; import java.util.regex.Pattern; import static java.lang.String.join; @@ -23,10 +27,12 @@ import static org.assertj.core.api.Assertions.assertThat; public class TestReport { public static final File BUILD_DOC_SCENARIOS = new File("build/doc/scenarios"); - private final static File markdownLogFile = new File(BUILD_DOC_SCENARIOS, ".last-debug-log.md"); public static final SimpleDateFormat MM_DD_YYYY_HH_MM_SS = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss"); - private final Map aliases; + private static final File markdownLogFile = new File(BUILD_DOC_SCENARIOS, ".last-debug-log.md"); + private static final ObjectMapper objectMapper = JsonObjectMapperConfiguration.build(); + + private final Map aliases; private final PrintWriter markdownLog; // records everything for debugging purposes private File markdownReportFile; private PrintWriter markdownReport; // records only the use-case under test, without its pre-requisites @@ -38,7 +44,7 @@ public class TestReport { } @SneakyThrows - public TestReport(final Map aliases) { + public TestReport(final Map aliases) { this.aliases = aliases; this.markdownLog = new PrintWriter(new FileWriter(markdownLogFile)); } @@ -76,6 +82,11 @@ public class TestReport { printLine("\n" +output + "\n"); } + @SneakyThrows + public void printJson(final String json) { + printLine(prettyJson(json)); + } + void silent(final Runnable code) { silent++; code.run(); @@ -100,6 +111,14 @@ public class TestReport { return convertedTestMethodName.replaceAll(": should ", ": "); } + private static String prettyJson(final String json) throws JsonProcessingException { + if (json == null) { + return ""; + } + final var jsonNode = objectMapper.readTree(json); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); + } + private String asClickableLink(final File file) { return file.toURI().toString().replace("file:/", "file:///"); } @@ -113,9 +132,8 @@ public class TestReport { final var result = new StringBuilder(); for (String line : lines) { - for (Map.Entry entry : aliases.entrySet()) { - final var uuidString = entry.getValue().toString(); - if (line.contains(uuidString)) { + for (Map.Entry entry : aliases.entrySet()) { + if ( entry.getValue() != null && line.contains(entry.getValue().toString())) { line = line + " // " + entry.getKey(); break; // only add comment for one UUID per row (in our case, there is only one per row) } @@ -151,4 +169,8 @@ public class TestReport { return "unknown"; } } + + public boolean isSilent() { + return silent > 0; + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java index 7b438134..01c5dede 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java @@ -122,29 +122,33 @@ public abstract class UseCase> { return new JsonTemplate(jsonTemplate); } - public final void obtain( + public final HttpResponse obtain( final String title, final Supplier http, final Function extractor, final String... extraInfo) { - withTitle(title, () -> { + return withTitle(title, () -> { final var response = http.get().keep(extractor); + response.optionallyReportRequestAndResponse(); Arrays.stream(extraInfo).forEach(testReport::printPara); return response; }); } - public final void obtain(final String alias, final Supplier http, final String... extraInfo) { - withTitle(alias, () -> { - final var response = http.get().keep(); + public final HttpResponse obtain(final String alias, final Supplier httpCall, final String... extraInfo) { + return withTitle(alias, () -> { + final var response = httpCall.get().keep(); + response.optionallyReportRequestAndResponse(); Arrays.stream(extraInfo).forEach(testReport::printPara); return response; }); } - public HttpResponse withTitle(final String resolvableTitle, final Supplier code) { + public HttpResponse withTitle(final String resolvableTitle, final Supplier httpCall, final String... extraInfo) { this.nextTitle = resolvableTitle; - final var response = code.get(); + final var response = httpCall.get(); + response.optionallyReportRequestAndResponse(); + Arrays.stream(extraInfo).forEach(testReport::printPara); this.nextTitle = null; return response; } @@ -261,6 +265,10 @@ public abstract class UseCase> { public final class HttpResponse { + private final HttpMethod httpMethod; + private final String uri; + private final String requestBody; + @Getter private final java.net.http.HttpResponse response; @@ -270,6 +278,9 @@ public abstract class UseCase> { @Getter private UUID locationUuid; + private boolean reportGenerated = false; + private boolean reportGeneratedWithResponse = false; + @SneakyThrows public HttpResponse( final HttpMethod httpMethod, @@ -277,6 +288,9 @@ public abstract class UseCase> { final String requestBody, final java.net.http.HttpResponse response ) { + this.httpMethod = httpMethod; + this.uri = uri; + this.requestBody = requestBody; this.response = response; this.status = HttpStatus.valueOf(response.statusCode()); if (this.status == HttpStatus.CREATED) { @@ -284,40 +298,42 @@ public abstract class UseCase> { assertThat(location).startsWith("http://localhost:"); locationUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1)); } - - reportRequestAndResponse(httpMethod, uri, requestBody); } public HttpResponse expecting(final HttpStatus httpStatus) { + optionallyReportRequestAndResponse(); assertThat(HttpStatus.valueOf(response.statusCode())).isEqualTo(httpStatus); return this; } public HttpResponse expecting(final ContentType contentType) { + optionallyReportRequestAndResponse(); assertThat(response.headers().firstValue("content-type")) .contains(contentType.toString()); return this; } public HttpResponse keep(final Function extractor) { + optionallyReportRequestAndResponse(); + final var alias = nextTitle != null ? ScenarioTest.resolve(nextTitle, DROP_COMMENTS) : resultAlias; assertThat(alias).as("cannot keep result, no alias found").isNotNull(); final var value = extractor.apply(this); - ScenarioTest.putAlias( - alias, - new ScenarioTest.Alias<>(UseCase.this.getClass(), UUID.fromString(value))); + ScenarioTest.putAlias(alias, UUID.fromString(value)); return this; } public HttpResponse keepAs(final String alias) { - ScenarioTest.putAlias( - nonNullAlias(alias), - new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid)); + optionallyReportRequestAndResponse(); + + ScenarioTest.putAlias(nonNullAlias(alias), locationUuid); return this; } public HttpResponse keep() { + optionallyReportRequestAndResponse(); + final var alias = nextTitle != null ? ScenarioTest.resolve(nextTitle, DROP_COMMENTS) : resultAlias; assertThat(alias).as("cannot keep result, no title or alias found for locationUuid: " + locationUuid).isNotNull(); @@ -326,6 +342,8 @@ public abstract class UseCase> { @SneakyThrows public HttpResponse expectArrayElements(final int expectedElementCount) { + optionallyReportRequestAndResponse(); + final var rootNode = objectMapper.readTree(response.body()); assertThat(rootNode.isArray()).as("array expected, but got: " + response.body()).isTrue(); @@ -335,6 +353,15 @@ public abstract class UseCase> { return this; } + @SneakyThrows + public HttpResponse expectObject() { + optionallyReportRequestAndResponse(); + + final var rootNode = objectMapper.readTree(response.body()); + assertThat(rootNode.isArray()).as("object expected, but got array: " + response.body()).isFalse(); + return this; + } + @SneakyThrows public V getFromBody(final String path) { final var body = response.body(); @@ -357,14 +384,23 @@ public abstract class UseCase> { return assertThat(getFromBodyAsOptional(path).givenAsString()); } + public HttpResponse reportWithResponse() { + return reportRequestAndResponse(true); + } + @SneakyThrows - private void reportRequestAndResponse(final HttpMethod httpMethod, final String uri, final String requestBody) { + private HttpResponse reportRequestAndResponse(final boolean unconditionallyWithResponse) { + if (reportGenerated) { + throw new IllegalStateException("request report already generated"); + } // the title if (nextTitle != null) { - testReport.printLine("\n### " + ScenarioTest.resolve(nextTitle, KEEP_COMMENTS) + "\n"); + testReport.printPara("### " + ScenarioTest.resolve(nextTitle, KEEP_COMMENTS)); } else if (resultAlias != null) { - testReport.printLine("\n### Create " + resultAlias + "\n"); + testReport.printPara("### Create " + resultAlias); + } else if (testReport.isSilent()) { + testReport.printPara("### Untitled Section"); } else { fail("please wrap the http...-call in the UseCase using `withTitle(...)`"); } @@ -372,17 +408,34 @@ public abstract class UseCase> { // the request testReport.printLine("```"); testReport.printLine(httpMethod.name() + " " + uri); - testReport.printLine((requestBody != null ? requestBody.trim() : "")); + testReport.printJson(requestBody); // the response testReport.printLine("=> status: " + status + " " + (locationUuid != null ? locationUuid : "")); - if (httpMethod == HttpMethod.GET || status.isError()) { - final var jsonNode = objectMapper.readTree(response.body()); - final var prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); - testReport.printLine(prettyJson); + if (unconditionallyWithResponse || httpMethod == HttpMethod.GET || status.isError()) { + testReport.printJson(response.body()); + this.reportGeneratedWithResponse = true; } testReport.printLine("```"); testReport.printLine(""); + this.reportGenerated = true; + return this; + } + + @SneakyThrows + private void optionallyReportRequestAndResponse() { + if (!reportGenerated) { + reportRequestAndResponse(false); + } + } + + private void verifyResponseReported(final String action) { + if (!reportGenerated) { + throw new IllegalStateException("report not generated yet, but expected for `" + action + "`"); + } + if (!reportGeneratedWithResponse) { + throw new IllegalStateException("report without response, but response report required for `" + action + "`"); + } } private String nonNullAlias(final String alias) { @@ -391,6 +444,24 @@ public abstract class UseCase> { final var onlyVisibleInGeneratedMarkdownNotInSource = new String(new char[]{'F', 'I', 'X', 'M', 'E'}); return alias == null ? "unknown alias -- " + onlyVisibleInGeneratedMarkdownNotInSource : alias; } + + public HttpResponse extractUuidAlias(final String jsonPath, final String resolvableName) { + verifyResponseReported("extractUuidAlias"); + + final var resolvedName = ScenarioTest.resolve(resolvableName, DROP_COMMENTS); + final var resolvedJsonPath = getFromBodyAsOptional(jsonPath).givenUUID(); + ScenarioTest.putAlias(resolvedName, resolvedJsonPath); + return this; + } + + public HttpResponse extractValue(final String jsonPath, final String resolvableName) { + verifyResponseReported("extractValue"); + + final var resolvedName = ScenarioTest.resolve(resolvableName, DROP_COMMENTS); + final var resolvedJsonPath = getFromBodyAsOptional(jsonPath).givenAsString(); + ScenarioTest.putProperty(resolvedName, resolvedJsonPath); + return this; + } } protected T self() { From d89b4b499266561018a01a47768487d2b4419221 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 23 Dec 2024 12:49:43 +0100 Subject: [PATCH 02/31] add CAS authentication (#138) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/138 Reviewed-by: Timotheus Pokorra --- .aliases | 2 + README.md | 43 ++++- bin/cas-curl | 165 ++++++++++++++++++ build.gradle | 1 + ...uthenticatedHttpServletRequestWrapper.java | 54 ++++++ .../config/AuthenticationFilter.java | 39 +++++ .../hsadminng/config/Authenticator.java | 8 + .../hsadminng/config/CasAuthenticator.java | 71 ++++++++ .../hsadminng/config/WebSecurityConfig.java | 9 + .../hsadminng/ping/PingController.java | 10 +- src/main/resources/application.yml | 3 + ...asAuthenticationFilterIntegrationTest.java | 88 ++++++++++ .../config/CasAuthenticatorUnitTest.java | 28 +++ .../CustomActuatorEndpointAcceptanceTest.java | 3 +- .../DisableSecurityConfig.java | 10 +- .../hsadminng/config/FakeAuthenticator.java | 14 ++ .../hsadminng/config/HttpHeadersBuilder.java | 12 ++ .../WebSecurityConfigIntegrationTest.java | 45 ++++- .../hsadminng/config/WireMockConfig.java | 21 +++ ...HsBookingItemControllerAcceptanceTest.java | 4 +- .../item/HsBookingItemControllerRestTest.java | 6 +- ...ookingProjectControllerAcceptanceTest.java | 4 +- ...sHostingAssetControllerAcceptanceTest.java | 6 +- .../HsHostingAssetControllerRestTest.java | 6 +- ...ingAssetPropsControllerAcceptanceTest.java | 4 +- ...ceBankAccountControllerAcceptanceTest.java | 4 +- ...HsOfficeBankAccountControllerRestTest.java | 2 +- ...OfficeContactControllerAcceptanceTest.java | 4 +- ...tsTransactionControllerAcceptanceTest.java | 4 +- ...opAssetsTransactionControllerRestTest.java | 4 +- ...esTransactionControllerAcceptanceTest.java | 4 +- ...opSharesTransactionControllerRestTest.java | 2 +- ...OfficeDebitorControllerAcceptanceTest.java | 4 +- ...iceMembershipControllerAcceptanceTest.java | 4 +- .../HsOfficeMembershipControllerRestTest.java | 4 +- ...OfficePartnerControllerAcceptanceTest.java | 4 +- .../HsOfficePartnerControllerRestTest.java | 2 +- ...sOfficePersonControllerAcceptanceTest.java | 4 +- ...fficeRelationControllerAcceptanceTest.java | 4 +- .../scenarios/HsOfficeScenarioTests.java | 2 +- ...ceSepaMandateControllerAcceptanceTest.java | 4 +- .../RbacGrantControllerAcceptanceTest.java | 2 +- .../RbacRoleControllerAcceptanceTest.java | 2 +- .../rbac/role/RbacRoleControllerRestTest.java | 2 +- .../RbacSubjectControllerAcceptanceTest.java | 2 +- .../RbacSubjectControllerRestTest.java | 2 +- .../TestCustomerControllerAcceptanceTest.java | 2 +- .../TestPackageControllerAcceptanceTest.java | 2 +- .../{rbac => }/test/JsonMatcher.java | 2 +- src/test/resources/application.yml | 4 + 50 files changed, 668 insertions(+), 64 deletions(-) create mode 100755 bin/cas-curl create mode 100644 src/main/java/net/hostsharing/hsadminng/config/AuthenticatedHttpServletRequestWrapper.java create mode 100644 src/main/java/net/hostsharing/hsadminng/config/AuthenticationFilter.java create mode 100644 src/main/java/net/hostsharing/hsadminng/config/Authenticator.java create mode 100644 src/main/java/net/hostsharing/hsadminng/config/CasAuthenticator.java create mode 100644 src/test/java/net/hostsharing/hsadminng/config/CasAuthenticationFilterIntegrationTest.java create mode 100644 src/test/java/net/hostsharing/hsadminng/config/CasAuthenticatorUnitTest.java rename src/test/java/net/hostsharing/hsadminng/{test => config}/DisableSecurityConfig.java (74%) create mode 100644 src/test/java/net/hostsharing/hsadminng/config/FakeAuthenticator.java create mode 100644 src/test/java/net/hostsharing/hsadminng/config/HttpHeadersBuilder.java create mode 100644 src/test/java/net/hostsharing/hsadminng/config/WireMockConfig.java rename src/test/java/net/hostsharing/hsadminng/{rbac => }/test/JsonMatcher.java (98%) diff --git a/.aliases b/.aliases index 2fb7e74e..cfd7c284 100644 --- a/.aliases +++ b/.aliases @@ -93,6 +93,8 @@ alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResource alias gw-test='. .aliases; ./gradlew test' alias gw-check='. .aliases; gw test check -x pitest' +alias cas-curl='bin/cas-curl' + # etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries alias gw-importOfficeData-in-docker-compose=' docker-compose -f etc/docker-compose.yml down && diff --git a/README.md b/README.md index 6be7fbe3..cfa7f45f 100644 --- a/README.md +++ b/README.md @@ -67,29 +67,37 @@ If you have at least Docker and the Java JDK installed in appropriate versions a gw scenarioTests # compiles and scenario-tests - takes ~1min on a decent machine # if the container has not been built yet, run this: - pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432 + pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432 + # if the container has been built already and you want to keep the data, run this: pg-sql-start - gw bootRun # compiles and runs the application on localhost:8080 +Next, compile and run the application without CAS-authentication on `localhost:8080`: + + export HSADMINNG_CAS_SERVER= + gw bootRun + +For using the REST-API with CAS-authentication, see `bin/cas-curl`. + +Now we can access the REST API, e.g. using curl: # the following command should reply with "pong": - curl -f http://localhost:8080/api/ping + curl -f -s http://localhost:8080/api/ping # the following command should return a JSON array with just all customers: - curl -f\ + curl -f -s\ -H 'current-subject: superuser-alex@hostsharing.net' \ http://localhost:8080/api/test/customers \ | jq # just if `jq` is installed, to prettyprint the output # the following command should return a JSON array with just all packages visible for the admin of the customer yyy: - curl -f\ + curl -f -s\ -H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: rbactest.customer#yyy:ADMIN' \ http://localhost:8080/api/test/packages \ | jq # add a new customer - curl -f\ + curl -f -s\ -H 'current-subject: superuser-alex@hostsharing.net' -H "Content-Type: application/json" \ -d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \ -X POST http://localhost:8080/api/test/customers \ @@ -807,6 +815,29 @@ postgres-autodoc The output will list the generated files. +### How to Add (Real) Admin Users + +```sql +DO $$ +DECLARE + -- replace with your admin account names + admin_users TEXT[] := ARRAY['admin-1', 'admin-2', 'admin-3']; + admin TEXT; +BEGIN + -- run as superuser + call base.defineContext('adding real admin users', null, null, null); + + -- for all new admin accounts + FOREACH admin IN ARRAY admin_users LOOP + call rbac.grantRoleToSubjectUnchecked( + rbac.findRoleId(rbac.global_ADMIN()), -- granted by role + rbac.findRoleId(rbac.global_ADMIN()), -- role to grant + rbac.create_subject(admin)); -- creates the new admin account + END LOOP; +END $$; +``` + + ## Further Documentation - the `doc` directory contains architecture concepts and a glossary diff --git a/bin/cas-curl b/bin/cas-curl new file mode 100755 index 00000000..41427a41 --- /dev/null +++ b/bin/cas-curl @@ -0,0 +1,165 @@ +#!/bin/bash + +if [ "$#" -eq 0 ] || [ "$1" == "help" ] || [ "$1" == "--help" ] || [ "$1" == "-h" ]; then + cat <> [parameters] + + commands: +EOF + grep '") ''# ' $0 + exit +fi + +if [ "$1" == "--trace" ]; then + function trace() { + echo "$*" >&2 + } + function doCurl() { + set -x + curl --fail-with-body --header "Authorization: $HSADMINNG_CAS_TICKET" "$@" + set +x + } + shift +else + function trace() { + : # noop + } + function doCurl() { + curl --fail-with-body --header "Authorization: $HSADMINNG_CAS_TICKET" "$@" + } +fi + +if [ -z "$HSADMINNG_CAS_LOGIN" ] || [ -z "$HSADMINNG_CAS_VALIDATE" ] || \ + [ -z "$HSADMINNG_CAS_SERVICE_ID" ]; then + cat >&2 <> + export HSADMINNG_CAS_SERVICE_ID=https://hsadminng.hostsharing.net:443/ +EOF + exit 1 +fi + +function casLogout() { + rm -f ~/.cas-login-tgt +} + +function casLogin() { + # ticket granting ticket exists and not expired? + if find ~/.cas-login-tgt -type f -size +0c -mmin -60 2>/dev/null | grep -q .; then + return + fi + + if [ -z "$HSADMINNG_CAS_USERNAME" ]; then + read -e -p "Username: " HSADMINNG_CAS_USERNAME + fi + + if [ -z "$HSADMINNG_CAS_PASSWORD" ]; then + read -s -e -p "Password: " HSADMINNG_CAS_PASSWORD + fi + + # Do NOT use doCurl here! We do neither want to print the password nor pass a CAS service ticket. + trace "+ curl --fail-with-body -s -i -X POST \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -d \"username=$HSADMINNG_CAS_USERNAME&password=<>\" \ + $HSADMINNG_CAS_LOGIN -o ~/.cas-login-tgt.response -D -" + HSADMINNG_CAS_TGT=`curl --fail-with-body -s -i -X POST \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -d "username=$HSADMINNG_CAS_USERNAME&password=$HSADMINNG_CAS_PASSWORD" \ + $HSADMINNG_CAS_LOGIN -o ~/.cas-login-tgt.response -D - \ + | grep -i "^Location: " | sed -e 's/^Location: //' -e 's/\\r//'` + if [ -z "$HSADMINNG_CAS_TGT" ]; then + echo "ERROR: could not get ticket granting ticket" >&2 + cat ~/.cas-login-tgt.response >&2 + fi + echo "$HSADMINNG_CAS_TGT" >~/.cas-login-tgt + trace "$HSADMINNG_CAS_TGT" +} + +function casTicket() { + HSADMINNG_CAS_TGT=$(<~/.cas-login-tgt) + if [[ -z "$HSADMINNG_CAS_TGT" ]]; then + echo "ERROR: cannot get CAS ticket granting ticket for $HSADMINNG_CAS_USERNAME" >&2 + exit 1 + fi + trace "CAS-TGT: $HSADMINNG_CAS_TGT" + + trace "fetching CAS service ticket" + trace "curl -s -d \"service=$HSADMINNG_CAS_SERVICE_ID\" $HSADMINNG_CAS_TGT" + HSADMINNG_CAS_TICKET=$(curl -s -d "service=$HSADMINNG_CAS_SERVICE_ID" $HSADMINNG_CAS_TGT) + if [[ -z "$HSADMINNG_CAS_TICKET" ]]; then + echo "ERROR: cannot get CAS service ticket" >&2 + exit 1 + fi + + echo $HSADMINNG_CAS_TICKET +} + +function casValidate() { + HSADMINNG_CAS_TICKET=`casTicket` + + trace "validating CAS-TICKET: $HSADMINNG_CAS_TICKET" + # Do NOT use doCurl here! We do not pass a CAS service ticket. + trace curl -i -s $HSADMINNG_CAS_VALIDATE?ticket=${HSADMINNG_CAS_TICKET}\&service=${HSADMINNG_CAS_SERVICE_ID} + HSADMINNG_CAS_USER=`curl -i -s $HSADMINNG_CAS_VALIDATE?ticket=${HSADMINNG_CAS_TICKET}\&service=${HSADMINNG_CAS_SERVICE_ID} | grep -oPm1 "(?<=)[^<]+"` + if [ -z "$HSADMINNG_CAS_USER" ]; then + echo "validation failed" >&2 + exit 1 + fi + echo "CAS-User: $HSADMINNG_CAS_USER" +} + +case "${1,,}" in + "login") # reads username+password and fetches ticket granting ticket (bypasses HSADMINNG_CAS_USERNAME+HSADMINNG_CAS_PASSWORD) + casLogout + export HSADMINNG_CAS_USERNAME= + export HSADMINNG_CAS_PASSWORD= + casLogin + ;; + "logout") # logout, deleting ticket granting ticket + casLogout + ;; + "validate") # validates ticket granting ticket and prints currently logged in user + casValidate + ;; + "get") # HTTP GET, add URL as parameter + shift + casLogin + HSADMINNG_CAS_TICKET=`casTicket` + doCurl "$*" + ;; + "post") # HTTP POST, add curl options to specify the request body and the URL as last parameter + shift + casLogin + HSADMINNG_CAS_TICKET=`casTicket` + doCurl --header "Content-Type: application/json" -X POST "$@" + ;; + "patch") # HTTP PATCH, add curl options to specify the request body and the URL as last parameter + shift + casLogin + HSADMINNG_CAS_TICKET=`casTicket` + doCurl --header "Content-Type: application/json" -X POST "$*" + ;; + "delete") # HTTP DELETE, add curl options to specify the request body and the URL as last parameter + shift + casLogin + HSADMINNG_CAS_TICKET=`casTicket` + curl -X POST "$@" + ;; + *) + cat >&2 < customHeaders = new HashMap<>(); + + public AuthenticatedHttpServletRequestWrapper(HttpServletRequest request) { + super(request); + } + + public void addHeader(final String name, final String value) { + customHeaders.put(name, value); + } + + @Override + public String getHeader(final String name) { + // Check custom headers first + final var customHeaderValue = customHeaders.get(name); + if (customHeaderValue != null) { + return customHeaderValue; + } + // Fall back to the original headers + return super.getHeader(name); + } + + @Override + public Enumeration getHeaderNames() { + // Combine original headers and custom headers + final var headerNames = new HashSet<>(customHeaders.keySet()); + final var originalHeaderNames = super.getHeaderNames(); + while (originalHeaderNames.hasMoreElements()) { + headerNames.add(originalHeaderNames.nextElement()); + } + return Collections.enumeration(headerNames); + } + + @Override + public Enumeration getHeaders(final String name) { + // Combine original headers and custom header + final var values = new HashSet(); + if (customHeaders.containsKey(name)) { + values.add(customHeaders.get(name)); + } + final var originalValues = super.getHeaders(name); + while (originalValues.hasMoreElements()) { + values.add(originalValues.nextElement()); + } + return Collections.enumeration(values); + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/config/AuthenticationFilter.java b/src/main/java/net/hostsharing/hsadminng/config/AuthenticationFilter.java new file mode 100644 index 00000000..1849b815 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/config/AuthenticationFilter.java @@ -0,0 +1,39 @@ +package net.hostsharing.hsadminng.config; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.stereotype.Component; + +@Component +public class AuthenticationFilter implements Filter { + + @Autowired + private Authenticator authenticator; + + @Override + @SneakyThrows + public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) { + final var httpRequest = (HttpServletRequest) request; + final var httpResponse = (HttpServletResponse) response; + + try { + final var currentSubject = authenticator.authenticate(httpRequest); + + final var authenticatedRequest = new AuthenticatedHttpServletRequestWrapper(httpRequest); + authenticatedRequest.addHeader("current-subject", currentSubject); + + chain.doFilter(authenticatedRequest, response); + } catch (final BadCredentialsException exc) { + // TODO.impl: should not be necessary if ResponseStatusException worked + httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/config/Authenticator.java b/src/main/java/net/hostsharing/hsadminng/config/Authenticator.java new file mode 100644 index 00000000..13f4ada4 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/config/Authenticator.java @@ -0,0 +1,8 @@ +package net.hostsharing.hsadminng.config; + +import jakarta.servlet.http.HttpServletRequest; + +public interface Authenticator { + + String authenticate(final HttpServletRequest httpRequest); +} diff --git a/src/main/java/net/hostsharing/hsadminng/config/CasAuthenticator.java b/src/main/java/net/hostsharing/hsadminng/config/CasAuthenticator.java new file mode 100644 index 00000000..e0d8a9f1 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/config/CasAuthenticator.java @@ -0,0 +1,71 @@ +package net.hostsharing.hsadminng.config; + +import io.micrometer.core.annotation.Timed; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.client.RestTemplate; +import org.xml.sax.SAXException; + +import jakarta.servlet.http.HttpServletRequest; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; + +public class CasAuthenticator implements Authenticator { + + @Value("${hsadminng.cas.server}") + private String casServerUrl; + + @Value("${hsadminng.cas.service}") + private String serviceUrl; + + private final RestTemplate restTemplate = new RestTemplate(); + + @SneakyThrows + @Timed("app.cas.authenticate") + public String authenticate(final HttpServletRequest httpRequest) { + final var userName = StringUtils.isBlank(casServerUrl) + ? bypassCurrentSubject(httpRequest) + : casValidation(httpRequest); + final var authentication = new UsernamePasswordAuthenticationToken(userName, null, null); + SecurityContextHolder.getContext().setAuthentication(authentication); + return authentication.getName(); + } + + private static String bypassCurrentSubject(final HttpServletRequest httpRequest) { + final var userName = httpRequest.getHeader("current-subject"); + System.err.println("CasAuthenticator.bypassCurrentSubject: " + userName); + return userName; + } + + 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; + + final var response = restTemplate.getForObject(url, String.class); + + final var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new java.io.ByteArrayInputStream(response.getBytes())); + if (doc.getElementsByTagName("cas:authenticationSuccess").getLength() == 0) { + // TODO.impl: for unknown reasons, this results in a 403 FORBIDDEN + // throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "CAS service ticket could not be validated"); + System.err.println("CAS service ticket could not be validated"); + System.err.println("CAS-validation-URL: " + url); + System.err.println(response); + throw new BadCredentialsException("CAS service ticket could not be validated"); + } + final var userName = doc.getElementsByTagName("cas:user").item(0).getTextContent(); + System.err.println("CAS-user: " + userName); + return userName; + } +} diff --git a/src/main/java/net/hostsharing/hsadminng/config/WebSecurityConfig.java b/src/main/java/net/hostsharing/hsadminng/config/WebSecurityConfig.java index 6da383ab..d279ae12 100644 --- a/src/main/java/net/hostsharing/hsadminng/config/WebSecurityConfig.java +++ b/src/main/java/net/hostsharing/hsadminng/config/WebSecurityConfig.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; @Configuration @@ -22,6 +23,14 @@ public class WebSecurityConfig { .requestMatchers("/actuator/**").permitAll() .anyRequest().authenticated() ) + .csrf(AbstractHttpConfigurer::disable) .build(); } + + @Bean + @Profile("!test") + public Authenticator casServiceTicketValidator() { + return new CasAuthenticator(); + } + } diff --git a/src/main/java/net/hostsharing/hsadminng/ping/PingController.java b/src/main/java/net/hostsharing/hsadminng/ping/PingController.java index f6f92f52..6ac6ff41 100644 --- a/src/main/java/net/hostsharing/hsadminng/ping/PingController.java +++ b/src/main/java/net/hostsharing/hsadminng/ping/PingController.java @@ -1,16 +1,22 @@ package net.hostsharing.hsadminng.ping; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; +import jakarta.validation.constraints.NotNull; + @Controller public class PingController { @ResponseBody @RequestMapping(value = "/api/ping", method = RequestMethod.GET) - public String ping() { - return "pong\n"; + public String ping( + @RequestHeader(name = "current-subject") @NotNull String currentSubject, + @RequestHeader(name = "assumed-roles", required = false) String assumedRoles + ) { + return "pong " + currentSubject + "\n"; } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f75ae429..69ad1e1b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,6 +36,9 @@ liquibase: hsadminng: postgres: leakproof: + cas: + server: https://login.hostsharing.net/cas # use empty string to bypass CAS-validation and directly use current-subject + service: https://hsadminng.hostsharing.net:443 # TODO.conf: deployment target + matching CAS service ID metrics: distribution: diff --git a/src/test/java/net/hostsharing/hsadminng/config/CasAuthenticationFilterIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/config/CasAuthenticationFilterIntegrationTest.java new file mode 100644 index 00000000..0c705e25 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/config/CasAuthenticationFilterIntegrationTest.java @@ -0,0 +1,88 @@ +package net.hostsharing.hsadminng.config; + +import com.github.tomakehurst.wiremock.WireMockServer; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static net.hostsharing.hsadminng.config.HttpHeadersBuilder.headers; +import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = "server.port=0") +@ActiveProfiles("wiremock") // IMPORTANT: To test prod config, do not use test profile! +class CasAuthenticationFilterIntegrationTest { + + @Value("${local.server.port}") + private int serverPort; + + @Value("${hsadminng.cas.service}") + private String serviceUrl; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private WireMockServer wireMockServer; + + @Test + public void shouldAcceptRequest() { + // given + final var username = "test-user-" + randomAlphanumeric(4); + wireMockServer.stubFor(get(urlEqualTo("/cas/p3/serviceValidate?service=" + serviceUrl + "&ticket=valid")) + .willReturn(aResponse() + .withStatus(200) + .withBody(""" + + + %{username} + + + """.replace("%{username}", username) + ))); + + // when + final var result = restTemplate.exchange( + "http://localhost:" + this.serverPort + "/api/ping", + HttpMethod.GET, + new HttpEntity<>(null, headers("Authorization", "valid")), + String.class + ); + + // then + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).isEqualTo("pong " + username + "\n"); + } + + @Test + public void shouldRejectRequest() { + // given + wireMockServer.stubFor(get(urlEqualTo("/cas/p3/serviceValidate?service=" + serviceUrl + "&ticket=invalid")) + .willReturn(aResponse() + .withStatus(200) + .withBody(""" + + + + """))); + + // when + final var result = restTemplate.exchange( + "http://localhost:" + this.serverPort + "/api/ping", + HttpMethod.GET, + new HttpEntity<>(null, headers("Authorization", "invalid")), + String.class + ); + + // then + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/config/CasAuthenticatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/config/CasAuthenticatorUnitTest.java new file mode 100644 index 00000000..5691e092 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/config/CasAuthenticatorUnitTest.java @@ -0,0 +1,28 @@ +package net.hostsharing.hsadminng.config; + +import org.junit.jupiter.api.Test; + +import jakarta.servlet.http.HttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +class CasAuthenticatorUnitTest { + + final CasAuthenticator casAuthenticator = new CasAuthenticator(); + + @Test + void bypassesAuthenticationIfNoCasServerIsConfigured() { + + // given + final var request = mock(HttpServletRequest.class); + given(request.getHeader("current-subject")).willReturn("given-user"); + + // when + final var userName = casAuthenticator.authenticate(request); + + // then + assertThat(userName).isEqualTo("given-user"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java index 1509831e..251804e2 100644 --- a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java @@ -2,13 +2,12 @@ package net.hostsharing.hsadminng.config; import io.restassured.RestAssured; import net.hostsharing.hsadminng.HsadminNgApplication; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalManagementPort; import org.springframework.test.context.ActiveProfiles; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, diff --git a/src/test/java/net/hostsharing/hsadminng/test/DisableSecurityConfig.java b/src/test/java/net/hostsharing/hsadminng/config/DisableSecurityConfig.java similarity index 74% rename from src/test/java/net/hostsharing/hsadminng/test/DisableSecurityConfig.java rename to src/test/java/net/hostsharing/hsadminng/config/DisableSecurityConfig.java index b0def144..4c4f98e8 100644 --- a/src/test/java/net/hostsharing/hsadminng/test/DisableSecurityConfig.java +++ b/src/test/java/net/hostsharing/hsadminng/config/DisableSecurityConfig.java @@ -1,7 +1,8 @@ -package net.hostsharing.hsadminng.test; +package net.hostsharing.hsadminng.config; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; @@ -10,10 +11,17 @@ import org.springframework.security.web.SecurityFilterChain; public class DisableSecurityConfig { @Bean + @Profile("test") public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) .csrf(AbstractHttpConfigurer::disable); return http.build(); } + + @Bean + @Profile("test") + public Authenticator fakeAuthenticator() { + return new FakeAuthenticator(); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/config/FakeAuthenticator.java b/src/test/java/net/hostsharing/hsadminng/config/FakeAuthenticator.java new file mode 100644 index 00000000..139ef053 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/config/FakeAuthenticator.java @@ -0,0 +1,14 @@ +package net.hostsharing.hsadminng.config; + +import lombok.SneakyThrows; + +import jakarta.servlet.http.HttpServletRequest; + +public class FakeAuthenticator implements Authenticator { + + @Override + @SneakyThrows + public String authenticate(final HttpServletRequest httpRequest) { + return httpRequest.getHeader("current-subject"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/config/HttpHeadersBuilder.java b/src/test/java/net/hostsharing/hsadminng/config/HttpHeadersBuilder.java new file mode 100644 index 00000000..ac61fb35 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/config/HttpHeadersBuilder.java @@ -0,0 +1,12 @@ +package net.hostsharing.hsadminng.config; + +import org.springframework.http.HttpHeaders; + +public class HttpHeadersBuilder { + + public static HttpHeaders headers(final String key, final String value) { + final var headers = new HttpHeaders(); + headers.set(key, value); + return headers; + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/config/WebSecurityConfigIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/config/WebSecurityConfigIntegrationTest.java index a69ca9f4..586702c2 100644 --- a/src/test/java/net/hostsharing/hsadminng/config/WebSecurityConfigIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/config/WebSecurityConfigIntegrationTest.java @@ -2,20 +2,28 @@ package net.hostsharing.hsadminng.config; import java.util.Map; +import com.github.tomakehurst.wiremock.WireMockServer; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(properties = {"management.port=0", "server.port=0"}) -// IMPORTANT: To test prod config, do not use test profile! +@ActiveProfiles("wiremock") // IMPORTANT: To test prod config, do not use test profile! class WebSecurityConfigIntegrationTest { @Value("${local.server.port}") @@ -24,15 +32,44 @@ class WebSecurityConfigIntegrationTest { @Value("${local.management.port}") private int managementPort; + @Value("${hsadminng.cas.service}") + private String serviceUrl; + @Autowired private TestRestTemplate restTemplate; + @Autowired + private WireMockServer wireMockServer; + @Test public void shouldSupportPingEndpoint() { - final var result = this.restTemplate.getForEntity( - "http://localhost:" + this.serverPort + "/api/ping", String.class); + // given + wireMockServer.stubFor(get(urlEqualTo("/cas/p3/serviceValidate?service=" + serviceUrl + "&ticket=test-user")) + .willReturn(aResponse() + .withStatus(200) + .withBody(""" + + + test-user + + + """))); + + + // fake Authorization header + final var headers = new HttpHeaders(); + headers.set("Authorization", "test-user"); + + // http request + final var result = restTemplate.exchange( + "http://localhost:" + this.serverPort + "/api/ping", + HttpMethod.GET, + new HttpEntity<>(null, headers), + String.class + ); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody()).startsWith("pong"); + assertThat(result.getBody()).startsWith("pong test-user"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/config/WireMockConfig.java b/src/test/java/net/hostsharing/hsadminng/config/WireMockConfig.java new file mode 100644 index 00000000..04f2371b --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/config/WireMockConfig.java @@ -0,0 +1,21 @@ +package net.hostsharing.hsadminng.config; + +import com.github.tomakehurst.wiremock.WireMockServer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@Profile("wiremock") +public class WireMockConfig { + + private static final WireMockServer wireMockServer = new WireMockServer(8088); + + @Bean + public WireMockServer wireMockServer() { + if (!wireMockServer.isRunning()) { + wireMockServer.start(); + } + return wireMockServer; + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java index 120f62f5..4715ad72 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerAcceptanceTest.java @@ -12,7 +12,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealRepository; import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.ClassOrderer; @@ -38,7 +38,7 @@ import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.matchesRegex; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerRestTest.java index 77e0abc8..7c0c68b0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemControllerRestTest.java @@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity; import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealRepository; import net.hostsharing.hsadminng.mapper.StrictMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -18,6 +18,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -29,7 +30,7 @@ import java.time.LocalDate; import java.util.Map; import java.util.UUID; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.hamcrest.Matchers.matchesRegex; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -41,6 +42,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @WebMvcTest(HsBookingItemController.class) @Import({StrictMapper.class, JsonObjectMapperConfiguration.class, DisableSecurityConfig.class}) @RunWith(SpringRunner.class) +@ActiveProfiles("test") class HsBookingItemControllerRestTest { @Autowired diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/project/HsBookingProjectControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/project/HsBookingProjectControllerAcceptanceTest.java index 737cc0a2..05313c56 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/project/HsBookingProjectControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/project/HsBookingProjectControllerAcceptanceTest.java @@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,7 +19,7 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import java.util.UUID; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.matchesRegex; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index 2d493fd0..9055148e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -14,7 +14,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.Nested; @@ -37,8 +37,8 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAI import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.strictlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.strictlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.matchesRegex; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java index 5aad9e2f..ffc97a63 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerRestTest.java @@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealRepository; import net.hostsharing.hsadminng.mapper.Array; import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -24,6 +24,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -43,7 +44,7 @@ import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.MANAGE import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetTestEntities.MANAGED_SERVER_HOSTING_ASSET_REAL_TEST_ENTITY; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetTestEntities.MANAGED_WEBSPACE_HOSTING_ASSET_REAL_TEST_ENTITY; import static net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealTestEntity.TEST_REAL_CONTACT; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; @@ -55,6 +56,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @WebMvcTest(HsHostingAssetController.class) @Import({ StandardMapper.class, JsonObjectMapperConfiguration.class, DisableSecurityConfig.class }) @RunWith(SpringRunner.class) +@ActiveProfiles("test") public class HsHostingAssetControllerRestTest { @Autowired diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java index b20a3c45..de7dd82d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java @@ -3,13 +3,13 @@ package net.hostsharing.hsadminng.hs.hosting.asset; import io.restassured.RestAssured; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.context.ActiveProfiles; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerAcceptanceTest.java index 0a565a60..1ce84ba9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerAcceptanceTest.java @@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.apache.commons.lang3.RandomStringUtils; import org.json.JSONException; import org.junit.jupiter.api.*; @@ -21,7 +21,7 @@ import jakarta.persistence.PersistenceContext; import java.util.UUID; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerRestTest.java index 7a699a25..a83558d6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountControllerRestTest.java @@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.hs.office.bankaccount; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.mapper.StandardMapper; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java index 645dea0e..a4b19eef 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java @@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.apache.commons.lang3.RandomStringUtils; import org.json.JSONException; import org.junit.jupiter.api.AfterEach; @@ -27,7 +27,7 @@ import java.util.concurrent.ThreadLocalRandom; import static java.util.Map.entry; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java index 1239c2a4..cceee9dd 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java @@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -26,7 +26,7 @@ import java.util.UUID; import static net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionType.DEPOSIT; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.startsWith; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerRestTest.java index c064a848..b9cb7aff 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerRestTest.java @@ -8,7 +8,7 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.mapper.StrictMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; import net.hostsharing.hsadminng.rbac.test.JsonBuilder; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import net.hostsharing.hsadminng.test.TestUuidGenerator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -38,7 +38,7 @@ import static net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsT import static net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionType.REVERSAL; import static net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionType.TRANSFER; import static net.hostsharing.hsadminng.rbac.test.JsonBuilder.jsonObject; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assumptions.assumeThat; import static org.hamcrest.Matchers.is; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java index 844e8db5..5568fa2f 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java @@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -25,7 +25,7 @@ import java.time.LocalDate; import java.util.UUID; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.startsWith; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java index 2b0129a3..5bc432de 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java @@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopshares; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.rbac.test.JsonBuilder; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; 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 a05687e4..ca9e7c84 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 @@ -12,7 +12,7 @@ import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -29,7 +29,7 @@ import java.util.UUID; import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java index b0f99593..88f29868 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java @@ -8,7 +8,7 @@ import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.json.JSONException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; @@ -27,7 +27,7 @@ import java.util.UUID; import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipStatus.ACTIVE; import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipStatus.CANCELLED; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.*; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java index 39af92bc..58271f57 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java @@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransact import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -24,7 +24,7 @@ import java.util.Optional; import java.util.UUID; import static io.hypersistence.utils.hibernate.type.range.Range.localDateRange; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; 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 e5ea3cca..941d2917 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 @@ -13,7 +13,7 @@ import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealReposito import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -25,7 +25,7 @@ import java.util.UUID; import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.EX_PARTNER; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.*; 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 4d016725..100d3852 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 @@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonControllerAcceptanceTest.java index 15307e4e..b0cae150 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonControllerAcceptanceTest.java @@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; @@ -22,7 +22,7 @@ import jakarta.persistence.PersistenceContext; import java.util.UUID; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.*; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java index d79ddbfa..c1ce47e4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java @@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationTypeResource; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -21,7 +21,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.UUID; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index f3037336..2f4ee9e5 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -39,7 +39,7 @@ import net.hostsharing.hsadminng.hs.scenarios.Produces; import net.hostsharing.hsadminng.hs.scenarios.Requires; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import net.hostsharing.hsadminng.test.IgnoreOnFailureExtension; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.Disabled; 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 ff5c4e46..ef334b0a 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 @@ -8,7 +8,7 @@ import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountReposi import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -26,7 +26,7 @@ import java.util.UUID; import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; -import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; +import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.*; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/grant/RbacGrantControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/grant/RbacGrantControllerAcceptanceTest.java index e2460d05..8f059572 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/grant/RbacGrantControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/grant/RbacGrantControllerAcceptanceTest.java @@ -10,7 +10,7 @@ import net.hostsharing.hsadminng.rbac.role.RbacRoleRepository; import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity; import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleControllerAcceptanceTest.java index ac925f2e..aa8c7727 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleControllerAcceptanceTest.java @@ -4,7 +4,7 @@ import io.restassured.RestAssured; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleControllerRestTest.java index f7507be1..d79a5578 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/role/RbacRoleControllerRestTest.java @@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.rbac.role; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/subject/RbacSubjectControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/subject/RbacSubjectControllerAcceptanceTest.java index 9392cc6c..45d8dac8 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/subject/RbacSubjectControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/subject/RbacSubjectControllerAcceptanceTest.java @@ -5,7 +5,7 @@ import io.restassured.http.ContentType; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/subject/RbacSubjectControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/subject/RbacSubjectControllerRestTest.java index e788c34b..917cd7ff 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/subject/RbacSubjectControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/subject/RbacSubjectControllerRestTest.java @@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.rbac.subject; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.mapper.StandardMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/test/cust/TestCustomerControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/test/cust/TestCustomerControllerAcceptanceTest.java index a022003b..1c210164 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/test/cust/TestCustomerControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/test/cust/TestCustomerControllerAcceptanceTest.java @@ -5,7 +5,7 @@ import io.restassured.http.ContentType; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/test/pac/TestPackageControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/test/pac/TestPackageControllerAcceptanceTest.java index 2ba2f086..4d68422e 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/test/pac/TestPackageControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/test/pac/TestPackageControllerAcceptanceTest.java @@ -4,7 +4,7 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; -import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import net.hostsharing.hsadminng.config.DisableSecurityConfig; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/test/JsonMatcher.java b/src/test/java/net/hostsharing/hsadminng/test/JsonMatcher.java similarity index 98% rename from src/test/java/net/hostsharing/hsadminng/rbac/test/JsonMatcher.java rename to src/test/java/net/hostsharing/hsadminng/test/JsonMatcher.java index 22ddead9..cf98f08b 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/test/JsonMatcher.java +++ b/src/test/java/net/hostsharing/hsadminng/test/JsonMatcher.java @@ -1,4 +1,4 @@ -package net.hostsharing.hsadminng.rbac.test; +package net.hostsharing.hsadminng.test; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index f0df4e4b..a69f8aa1 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -51,3 +51,7 @@ testcontainers: network: mode: host +hsadminng: + cas: + server: http://localhost:8088/cas # mocked via WireMock + service: http://localhost:8080/api # must match service used in WireMock mock response From 9debaa1fc00762665181895b5632c2a4f2b48487 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 30 Dec 2024 10:00:12 +0100 Subject: [PATCH 03/31] assuming-long-roleidnames + object-uuid-based-rolenames (#139) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/139 Reviewed-by: Timotheus Pokorra --- bin/cas-curl | 120 ++++++++++++++--- .../hsadminng/context/Context.java | 2 +- .../HsOfficeRelationRbacRepository.java | 18 +-- .../HsOfficeRelationRealRepository.java | 63 +++++++-- .../db/changelog/0-base/010-context.sql | 6 +- .../db/changelog/1-rbac/1050-rbac-base.sql | 12 +- .../db/changelog/1-rbac/1054-rbac-context.sql | 15 ++- .../5028-hs-office-person-test-data.sql | 3 +- .../5038-hs-office-relation-test-data.sql | 6 +- .../5048-hs-office-partner-test-data.sql | 2 +- .../5058-hs-office-bankaccount-test-data.sql | 4 +- .../5068-hs-office-debitor-test-data.sql | 2 +- ...ceBankAccountControllerAcceptanceTest.java | 2 +- ...eBankAccountRepositoryIntegrationTest.java | 2 +- ...OfficeDebitorControllerAcceptanceTest.java | 6 +- ...fficeDebitorRepositoryIntegrationTest.java | 2 +- ...OfficePartnerControllerAcceptanceTest.java | 2 +- ...fficePartnerRepositoryIntegrationTest.java | 2 +- ...cePersonRbacRepositoryIntegrationTest.java | 8 +- ...cePersonRealRepositoryIntegrationTest.java | 8 +- ...RealRelationRepositoryIntegrationTest.java | 8 +- ...fficeRelationControllerAcceptanceTest.java | 2 +- ...ficeRelationRepositoryIntegrationTest.java | 127 ++++++++++++------ ...ceSepaMandateControllerAcceptanceTest.java | 2 +- .../rbac/context/ContextUnitTest.java | 2 +- .../RbacRoleRepositoryIntegrationTest.java | 2 +- 26 files changed, 301 insertions(+), 127 deletions(-) diff --git a/bin/cas-curl b/bin/cas-curl index 41427a41..45fa22dd 100755 --- a/bin/cas-curl +++ b/bin/cas-curl @@ -1,14 +1,10 @@ #!/bin/bash -if [ "$#" -eq 0 ] || [ "$1" == "help" ] || [ "$1" == "--help" ] || [ "$1" == "-h" ]; then - cat <> [parameters] - - commands: -EOF - grep '") ''# ' $0 - exit +if [ "$2" == "--show-password" ]; then + HSADMINNG_CAS_SHOW_PASSWORD=yes + shift +else + HSADMINNG_CAS_SHOW_PASSWORD= fi if [ "$1" == "--trace" ]; then @@ -17,7 +13,10 @@ if [ "$1" == "--trace" ]; then } function doCurl() { set -x - curl --fail-with-body --header "Authorization: $HSADMINNG_CAS_TICKET" "$@" + curl --fail-with-body \ + --header "Authorization: $HSADMINNG_CAS_TICKET" \ + --header "assumed-roles: $HSADMINNG_CAS_ASSUME" \ + "$@" set +x } shift @@ -30,6 +29,13 @@ else } fi +export HSADMINNG_CAS_ASSUME_HEADER +if [ -f ~/.cas-curl-assume ]; then + HSADMINNG_CAS_ASSUME="$(cat ~/.cas-curl-assume)" +else + HSADMINNG_CAS_ASSUME= +fi + if [ -z "$HSADMINNG_CAS_LOGIN" ] || [ -z "$HSADMINNG_CAS_VALIDATE" ] || \ [ -z "$HSADMINNG_CAS_SERVICE_ID" ]; then cat >&2 <> [parameters] + + commands: +EOF + # filters out help texts (containing double-# and following lines with leading single-#) from the commands itself + # (the '' makes sure that this line is not found, just the lines with actual help texts) + sed -n '/#''#/ {x; p; x; s/#''#//; p; :a; n; /^[[:space:]]*#/!b; s/^[[:space:]]*#//; p; ba}' <$0 } function casLogin() { @@ -63,10 +77,16 @@ function casLogin() { read -s -e -p "Password: " HSADMINNG_CAS_PASSWORD fi + if [ "$HSADMINNG_CAS_SHOW_PASSWORD" == "--show-password" ]; then + HSADMINNG_CAS_PASSWORD_DISPLAY=$HSADMINNG_CAS_PASSWORD + else + HSADMINNG_CAS_PASSWORD_DISPLAY="<