From 6a01002a05592f6fb3524694c16874d1b7067d19 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 19 Feb 2024 13:19:57 +0100 Subject: [PATCH] all direct partner tests green --- doc/rbac.md | 2 +- .../partner/HsOfficePartnerController.java | 4 +- .../partner/HsOfficePartnerEntityPatcher.java | 2 +- .../partner/HsOfficePartnerRepository.java | 4 +- .../rbacgrant/RbacGrantsMermaidService.java | 92 +++++++------- .../hs-office/hs-office-partner-schemas.yaml | 2 +- ...OfficePartnerControllerAcceptanceTest.java | 113 ++++++++++++------ .../HsOfficePartnerControllerRestTest.java | 26 ---- .../HsOfficePartnerEntityPatcherUnitTest.java | 6 +- ...fficePartnerRepositoryIntegrationTest.java | 43 ++----- ...acGrantsMermaidServiceIntegrationTest.java | 1 - .../rbac/rbacrole/RawRbacObjectEntity.java | 31 +++++ .../rbacrole/RawRbacObjectRepository.java | 11 ++ 13 files changed, 190 insertions(+), 147 deletions(-) create mode 100644 src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectEntity.java create mode 100644 src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectRepository.java diff --git a/doc/rbac.md b/doc/rbac.md index 06a6ee7e..6850fb6f 100644 --- a/doc/rbac.md +++ b/doc/rbac.md @@ -1,6 +1,6 @@ ## *hsadmin-ng*'s Role-Based-Access-Management (RBAC) -The requirements of *hsadmin-ng* include table-m row- and column-level-security for read and write access to business-objects. +The requirements of *hsadmin-ng* option table-m row- and column-level-security for read and write access to business-objects. More precisely, any access has to be controlled according to given rules depending on the accessing users, their roles and the accessed business-object. Further, roles and business-objects are hierarchical. diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java index 5aeb6911..19e32f71 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java @@ -110,9 +110,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi { return ResponseEntity.notFound().build(); } - if (partnerRepo.deleteByUuid(partnerUuid) != 1 || - // TODO: move to after delete trigger in partner - relationshipRepo.deleteByUuid(partnerToDelete.get().getPartnerRole().getUuid()) != 1 ) { + if (partnerRepo.deleteByUuid(partnerUuid) != 1) { return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityPatcher.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityPatcher.java index b8c377b4..3c70a0da 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityPatcher.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityPatcher.java @@ -20,7 +20,7 @@ class HsOfficePartnerEntityPatcher implements EntityPatcher { - verifyNotNull(newValue, "contact"); + verifyNotNull(newValue, "partnerRole"); entity.setPartnerRole(em.getReference(HsOfficeRelationshipEntity.class, newValue)); }); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java index 665202f5..47e6383b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java @@ -11,11 +11,13 @@ public interface HsOfficePartnerRepository extends Repository findByUuid(UUID id); + List findAll(); // TODO: move to a repo in test sources + @Query(""" SELECT partner FROM HsOfficePartnerEntity partner JOIN HsOfficeRelationshipEntity rel ON rel.uuid = partner.partnerRole.uuid JOIN HsOfficeContactEntity contact ON contact.uuid = rel.contact.uuid - JOIN HsOfficePersonEntity person ON person.uuid = rel.relAnchor.uuid + JOIN HsOfficePersonEntity person ON person.uuid = rel.relHolder.uuid WHERE :name is null OR partner.details.birthName like concat(cast(:name as text), '%') OR contact.label like concat(cast(:name as text), '%') diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsMermaidService.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsMermaidService.java index 58d6e0eb..7ddefbb6 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsMermaidService.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsMermaidService.java @@ -37,6 +37,7 @@ public class RbacGrantsMermaidService { } public enum Include { + DETAILS, USERS, PERMISSIONS, NOT_ASSUMED, @@ -53,84 +54,91 @@ public class RbacGrantsMermaidService { @PersistenceContext private EntityManager em; - public String allGrantsToCurrentUser(final EnumSet include) { + public String allGrantsToCurrentUser(final EnumSet includes) { final var graph = new HashSet(); for ( UUID subjectUuid: context.currentSubjectsUuids() ) { - traverseGrantsTo(graph, subjectUuid, include); + traverseGrantsTo(graph, subjectUuid, includes); } - return toMermaidFlowchart(graph); + return toMermaidFlowchart(graph, includes); } - private void traverseGrantsTo(final Set graph, final UUID refUuid, final EnumSet include) { + private void traverseGrantsTo(final Set graph, final UUID refUuid, final EnumSet includes) { final var grants = rawGrantRepo.findByAscendingUuid(refUuid); grants.forEach(g -> { - if (!include.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) { + if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) { return; } - if (!include.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(" test_")) { + if (!includes.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(" test_")) { return; } - if (!include.contains(NON_TEST_ENTITIES) && !g.getDescendantIdName().contains(" test_")) { + if (!includes.contains(NON_TEST_ENTITIES) && !g.getDescendantIdName().contains(" test_")) { return; } graph.add(g); - if (include.contains(NOT_ASSUMED) || g.isAssumed()) { - traverseGrantsTo(graph, g.getDescendantUuid(), include); + if (includes.contains(NOT_ASSUMED) || g.isAssumed()) { + traverseGrantsTo(graph, g.getDescendantUuid(), includes); } }); } - public String allGrantsFrom(final UUID targetObject, final String op, final EnumSet include) { + public String allGrantsFrom(final UUID targetObject, final String op, final EnumSet includes) { final var refUuid = (UUID) em.createNativeQuery("SELECT uuid FROM rbacpermission WHERE objectuuid=:targetObject AND op=:op") .setParameter("targetObject", targetObject) .setParameter("op", op) .getSingleResult(); final var graph = new HashSet(); - traverseGrantsFrom(graph, refUuid, include); - return toMermaidFlowchart(graph); + traverseGrantsFrom(graph, refUuid, includes); + return toMermaidFlowchart(graph, includes); } - private void traverseGrantsFrom(final Set graph, final UUID refUuid, final EnumSet include) { + private void traverseGrantsFrom(final Set graph, final UUID refUuid, final EnumSet option) { final var grants = rawGrantRepo.findByDescendantUuid(refUuid); grants.forEach(g -> { - if (!include.contains(USERS) && g.getAscendantIdName().startsWith("user ")) { + if (!option.contains(USERS) && g.getAscendantIdName().startsWith("user ")) { return; } graph.add(g); - if (include.contains(NOT_ASSUMED) || g.isAssumed()) { - traverseGrantsFrom(graph, g.getAscendingUuid(), include); + if (option.contains(NOT_ASSUMED) || g.isAssumed()) { + traverseGrantsFrom(graph, g.getAscendingUuid(), option); } }); } - private String toMermaidFlowchart(final HashSet graph) { - final var entities = graph.stream() - .flatMap(g -> Stream.of( - new Node(g.getAscendantIdName(), g.getAscendingUuid()), - new Node(g.getDescendantIdName(), g.getDescendantUuid())) - ) - .collect(groupingBy(RbacGrantsMermaidService::entityIdName)); - - return "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n" + - "flowchart TB\n\n" - + entities.entrySet().stream() - .map(entity -> "subgraph " + quoted(entity.getKey()) + subgraphDisplay(entity.getKey()) + "\n\n " + private String toMermaidFlowchart(final HashSet graph, final EnumSet includes) { + final var entities = + includes.contains(DETAILS) + ? graph.stream() + .flatMap(g -> Stream.of( + new Node(g.getAscendantIdName(), g.getAscendingUuid()), + new Node(g.getDescendantIdName(), g.getDescendantUuid())) + ) + .collect(groupingBy(RbacGrantsMermaidService::renderEntityIdName)) + .entrySet().stream() + .map(entity -> "subgraph " + quoted(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n " + entity.getValue().stream() - .map(n -> node(n.idName(), n.uuid()).replace("\n", "\n ")) - .sorted() - .distinct() - .collect(joining("\n\n "))) + .map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n ")) + .sorted() + .distinct() + .collect(joining("\n\n "))) .collect(joining("\n\nend\n\n")) - + "\n\nend\n\n" - + graph.stream() - .map(g -> quoted(g.getAscendantIdName()) + + + "\n\nend\n\n" + : ""; + + final var grants = graph.stream() + .map(g -> quoted(g.getAscendantIdName()) + (g.isAssumed() ? " --> " : " -.-> ") + quoted(g.getDescendantIdName())) - .sorted() - .collect(joining("\n")); + .sorted() + .collect(joining("\n")); + + final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n"; + return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "") + + "flowchart TB\n\n" + + entities + + grants; } - private String subgraphDisplay(final String entityId) { + private String renderSubgraph(final String entityId) { // this does not work according to Mermaid bug https://github.com/mermaid-js/mermaid/issues/3806 // if (entityId.contains("#")) { // final var parts = entityId.split("#"); @@ -144,7 +152,7 @@ public class RbacGrantsMermaidService { return "[" + entityId + "]"; } - private static String entityIdName(final Node node) { + private static String renderEntityIdName(final Node node) { final var refType = refType(node.idName()); if (refType.equals("user")) { return "users"; @@ -159,11 +167,11 @@ public class RbacGrantsMermaidService { throw new IllegalArgumentException("unknown refType '" + refType + "' in '" + node.idName() + "'"); } - private String node(final String idName, final UUID uuid) { - return quoted(idName) + nodeContent(idName, uuid); + private String renderNode(final String idName, final UUID uuid) { + return quoted(idName) + renderNodeContent(idName, uuid); } - private String nodeContent(final String idName, final UUID uuid) { + private String renderNodeContent(final String idName, final UUID uuid) { final var refType = refType(idName); if (refType.equals("user")) { diff --git a/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml index 02986cfc..09e49c6b 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml @@ -50,7 +50,7 @@ components: HsOfficePartnerPatch: type: object properties: - partnerRoleUUid: + partnerRoleUuid: type: string format: uuid nullable: true 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 b78b5c18..8d4f1ff3 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 @@ -19,6 +19,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.transaction.annotation.Transactional; +import java.util.EnumSet; import java.util.UUID; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; @@ -91,9 +92,9 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu void globalAdmin_withoutAssumedRole_canAddPartner() { context.define("superuser-alex@hostsharing.net"); - final var givenMandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0); - final var givenPerson = personRepo.findPersonByOptionalNameLike("Third").get(0); - final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); + final var givenMandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").stream().findFirst().orElseThrow(); + final var givenPerson = personRepo.findPersonByOptionalNameLike("Third").stream().findFirst().orElseThrow(); + final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").stream().findFirst().orElseThrow(); final var location = RestAssured // @formatter:off .given() @@ -107,8 +108,6 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu "relHolderUuid": "%s", "contactUuid": "%s" }, - "personUuid": "%s", - "contactUuid": "%s", "details": { "registrationOffice": "Temp Registergericht Aurich", "registrationNumber": "111111" @@ -117,21 +116,29 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu """.formatted( givenMandantPerson.getUuid(), givenPerson.getUuid(), - givenContact.getUuid(), - givenPerson.getUuid(), givenContact.getUuid())) .port(port) .when() .post("http://localhost/api/hs/office/partners") - .then().assertThat() + .then().log().body().assertThat() .statusCode(201) .contentType(ContentType.JSON) - .body("uuid", isUuidValid()) - .body("partnerNumber", is(20002)) - .body("details.registrationOffice", is("Temp Registergericht Aurich")) - .body("details.registrationNumber", is("111111")) - .body("contact.label", is(givenContact.getLabel())) - .body("person.tradeName", is(givenPerson.getTradeName())) + .body("", lenientlyEquals(""" + { + "partnerNumber": 20002, + "partnerRole": { + "relAnchor": { "tradeName": "Hostsharing eG" }, + "relHolder": { "tradeName": "Third OHG" }, + "relType": "PARTNER", + "relMark": null, + "contact": { "label": "fourth contact" } + }, + "details": { + "registrationOffice": "Temp Registergericht Aurich", + "registrationNumber": "111111" + } + } + """)) .header("Location", startsWith("http://localhost")) .extract().header("Location"); // @formatter:on @@ -226,6 +233,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu @Test void globalAdmin_withoutAssumedRole_canGetArbitraryPartner() { context.define("superuser-alex@hostsharing.net"); + final var partners = partnerRepo.findAll(); final var givenPartnerUuid = partnerRepo.findPartnerByOptionalNameLike("First").get(0).getUuid(); RestAssured // @formatter:off @@ -239,8 +247,18 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu .contentType("application/json") .body("", lenientlyEquals(""" { - "person": { "tradeName": "First GmbH" }, - "contact": { "label": "first contact" } + "partnerNumber": 10001, + "partnerRole": { + "relAnchor": { "tradeName": "Hostsharing eG" }, + "relHolder": { "tradeName": "First GmbH" }, + "relType": "PARTNER", + "contact": { "label": "first contact" } + }, + "details": { + "registrationOffice": "Hamburg", + "registrationNumber": "RegNo123456789" + } + } } """)); // @formatter:on } @@ -278,8 +296,10 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu .contentType("application/json") .body("", lenientlyEquals(""" { - "person": { "tradeName": "First GmbH" }, - "contact": { "label": "first contact" } + "partnerRole": { + "relHolder": { "tradeName": "First GmbH" }, + "contact": { "label": "first contact" } + } } """)); // @formatter:on } @@ -295,8 +315,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu context.define("superuser-alex@hostsharing.net"); final var givenPartner = givenSomeTemporaryPartnerBessler(20011); - final var givenPerson = personRepo.findPersonByOptionalNameLike("Third").get(0); - final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); + final var givenPartnerRel = givenSomeTemporaryPartnerRel("Third OHG", "third contact"); RestAssured // @formatter:off .given() @@ -305,8 +324,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu .body(""" { "partnerNumber": "20011", - "contactUuid": "%s", - "personUuid": "%s", + "partnerRoleUuid": "%s", "details": { "registrationOffice": "Temp Registergericht Aurich", "registrationNumber": "222222", @@ -315,18 +333,32 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu "dateOfDeath": "2022-01-12" } } - """.formatted(givenContact.getUuid(), givenPerson.getUuid())) + """.formatted(givenPartnerRel.getUuid())) .port(port) .when() .patch("http://localhost/api/hs/office/partners/" + givenPartner.getUuid()) - .then().assertThat() + .then().log().body().assertThat() .statusCode(200) .contentType(ContentType.JSON) - .body("uuid", is(givenPartner.getUuid().toString())) // not patched! - .body("partnerNumber", is(givenPartner.getPartnerNumber())) // not patched! - .body("details.registrationNumber", is("222222")) - .body("contact.label", is(givenContact.getLabel())) - .body("person.tradeName", is(givenPerson.getTradeName())); + .body("", lenientlyEquals(""" + { + "partnerNumber": 20011, + "partnerRole": { + "relAnchor": { "tradeName": "Hostsharing eG" }, + "relHolder": { "tradeName": "Third OHG" }, + "relType": "PARTNER", + "contact": { "label": "third contact" } + }, + "details": { + "registrationOffice": "Temp Registergericht Aurich", + "registrationNumber": "222222", + "birthName": "Maja Schmidt", + "birthPlace": null, + "birthday": "1938-04-08", + "dateOfDeath": "2022-01-12" + } + } + """)); // @formatter:on // finally, the partner is actually updated @@ -334,9 +366,8 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu assertThat(partnerRepo.findByUuid(givenPartner.getUuid())).isPresent().get() .matches(partner -> { assertThat(partner.getPartnerNumber()).isEqualTo(givenPartner.getPartnerNumber()); - // TODO: assert partnerRole -// assertThat(partner.getPerson().getTradeName()).isEqualTo("Third OHG"); -// assertThat(partner.getContact().getLabel()).isEqualTo("fourth contact"); + assertThat(partner.getPartnerRole().getRelHolder().getTradeName()).isEqualTo("Third OHG"); + assertThat(partner.getPartnerRole().getContact().getLabel()).isEqualTo("third contact"); assertThat(partner.getDetails().getRegistrationOffice()).isEqualTo("Temp Registergericht Aurich"); assertThat(partner.getDetails().getRegistrationNumber()).isEqualTo("222222"); assertThat(partner.getDetails().getBirthName()).isEqualTo("Maja Schmidt"); @@ -460,13 +491,14 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu } } - private HsOfficePartnerEntity givenSomeTemporaryPartnerBessler(final Integer partnerNumber) { + private HsOfficeRelationshipEntity givenSomeTemporaryPartnerRel( + final String partnerHolderName, + final String contactName) { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); - final var givenMandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0); - - final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0); - final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0); + final var givenMandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").stream().findFirst().orElseThrow(); + final var givenPerson = personRepo.findPersonByOptionalNameLike(partnerHolderName).stream().findFirst().orElseThrow(); + final var givenContact = contactRepo.findContactByOptionalLabelLike(contactName).stream().findFirst().orElseThrow(); final var partnerRole = new HsOfficeRelationshipEntity(); partnerRole.setRelType(HsOfficeRelationshipType.PARTNER); @@ -474,6 +506,13 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu partnerRole.setRelHolder(givenPerson); partnerRole.setContact(givenContact); em.persist(partnerRole); + return partnerRole; + }).assertSuccessful().returnedValue(); + } + private HsOfficePartnerEntity givenSomeTemporaryPartnerBessler(final Integer partnerNumber) { + return jpaAttempt.transacted(() -> { + context.define("superuser-alex@hostsharing.net"); + final var partnerRole = em.merge(givenSomeTemporaryPartnerRel("Erben Bessler", "fourth contact")); final var newPartner = HsOfficePartnerEntity.builder() .partnerRole(partnerRole) 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 ed04d899..331ca5db 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 @@ -191,31 +191,5 @@ class HsOfficePartnerControllerRestTest { // then .andExpect(status().isForbidden()); } - - @Test - void respondBadRequest_ifRelationshipCannotBeDeleted() throws Exception { - // given - final UUID givenPartnerUuid = UUID.randomUUID(); - when(partnerRepo.findByUuid(givenPartnerUuid)).thenReturn(Optional.of(partnerMock)); - when(partnerRepo.deleteByUuid(givenPartnerUuid)).thenReturn(1); - when(relationshipRepo.deleteByUuid(any())).thenReturn(0); - - final UUID givenRelationshipUuid = UUID.randomUUID(); - when(partnerMock.getPartnerRole()).thenReturn(HsOfficeRelationshipEntity.builder() - .uuid(givenRelationshipUuid) - .build()); - when(relationshipRepo.deleteByUuid(givenRelationshipUuid)).thenReturn(0); - - // when - mockMvc.perform(MockMvcRequestBuilders - .delete("/api/hs/office/partners/" + givenPartnerUuid) - .header("current-user", "superuser-alex@hostsharing.net") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - - // then - .andExpect(status().isForbidden()); - } - } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityPatcherUnitTest.java index 0342e7ca..f82d258b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityPatcherUnitTest.java @@ -48,10 +48,8 @@ class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase< @BeforeEach void initMocks() { - lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> - HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); - lenient().when(em.getReference(eq(HsOfficePersonEntity.class), any())).thenAnswer(invocation -> - HsOfficePersonEntity.builder().uuid(invocation.getArgument(1)).build()); + lenient().when(em.getReference(eq(HsOfficeRelationshipEntity.class), any())).thenAnswer(invocation -> + HsOfficeRelationshipEntity.builder().uuid(invocation.getArgument(1)).build()); } @Override diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index 5a52a97d..661f9c1d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -8,8 +8,7 @@ import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepo import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; -import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsMermaidService; -import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsMermaidService.Include; +import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.test.Array; import net.hostsharing.test.JpaAttempt; @@ -26,19 +25,18 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; -import java.util.EnumSet; import java.util.HashSet; import java.util.List; -import static java.lang.String.join; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; +import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectEntity.objectDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.test.Array.fromFormatted; import static net.hostsharing.test.JpaAttempt.attempt; import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest -@Import( { Context.class, JpaAttempt.class, RbacGrantsMermaidService.class }) +@Import( { Context.class, JpaAttempt.class }) class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithCleanup { @Autowired @@ -53,15 +51,15 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean @Autowired HsOfficeContactRepository contactRepo; + @Autowired + RawRbacObjectRepository rawObjectRepo; + @Autowired RawRbacRoleRepository rawRoleRepo; @Autowired RawRbacGrantRepository rawGrantRepo; - @Autowired - RbacGrantsMermaidService mermaidService; - @PersistenceContext EntityManager em; @@ -263,9 +261,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean givenPartner, "hs_office_person#ErbenBesslerMelBessler.admin"); assertThatPartnerActuallyInDatabase(givenPartner); - RbacGrantsMermaidService.writeToFile("initial partner: Erben Bessler + fifth contact", - mermaidService.allGrantsFrom(givenPartner.getUuid(), "view", EnumSet.of(Include.USERS)), - "doc/all-grants-before-update.md"); // when @@ -277,9 +272,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean // then result.assertSuccessful(); - RbacGrantsMermaidService.writeToFile("updated partner: Third OHG + sixth contact", - mermaidService.allGrantsFrom(result.returnedValue().getUuid(), "view", EnumSet.of(Include.USERS)), - "doc/all-grants-after-update.md"); assertThatPartnerIsVisibleForUserWithRole( result.returnedValue(), "global#global.admin"); @@ -298,13 +290,13 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean final var givenPartner = givenSomeTemporaryHostsharingPartner(20037, "Erben Bessler", "ninth"); assertThatPartnerIsVisibleForUserWithRole( givenPartner, - "hs_office_partner#20033:ErbenBesslerMelBessler-ninthcontact.agent"); + "hs_office_person#ErbenBesslerMelBessler.admin"); assertThatPartnerActuallyInDatabase(givenPartner); // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net", - "hs_office_partner#20033:ErbenBesslerMelBessler-ninthcontact.agent"); + "hs_office_person#ErbenBesslerMelBessler.admin"); givenPartner.getDetails().setBirthName("new birthname"); return partnerRepo.save(givenPartner); }); @@ -316,8 +308,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean private void assertThatPartnerActuallyInDatabase(final HsOfficePartnerEntity saved) { final var found = partnerRepo.findByUuid(saved.getUuid()); - found.get().getPartnerRole(); // TODO: remove and uncomment the next line: - // assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved); + assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved); } private void assertThatPartnerIsVisibleForUserWithRole( @@ -334,10 +325,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean final String assumedRoles) { jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net", assumedRoles); - RbacGrantsMermaidService.writeToFile("partner visible in assertThatPartnerIsNotVisibleForUserWithRole", - mermaidService.allGrantsFrom(entity.getUuid(), "view", EnumSet.of(Include.USERS)), - "doc/all-grants-within-assertThatPartnerIsNotVisibleForUserWithRole.md"); - final var found = partnerRepo.findByUuid(entity.getUuid()); assertThat(found).isEmpty(); }).assertSuccessful(); @@ -395,6 +382,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean public void deletingAPartnerAlsoDeletesRelatedRolesAndGrants() { // given context("superuser-alex@hostsharing.net"); + final var initialObjects = Array.from(objectDisplaysOf(rawObjectRepo.findAll())); final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll())); final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll())); final var givenPartner = givenSomeTemporaryHostsharingPartner(20034, "Erben Bessler", "twelfth"); @@ -402,15 +390,13 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - // TODO: should deleting a partner automatically delete the PARTNER relationship? (same for debitor) - // TODO: why did the test cleanup check does not notice this, if missing? - return partnerRepo.deleteByUuid(givenPartner.getUuid()) + - relationshipRepo.deleteByUuid(givenPartner.getPartnerRole().getUuid()); + return partnerRepo.deleteByUuid(givenPartner.getUuid()); }); // then result.assertSuccessful(); - assertThat(result.returnedValue()).isEqualTo(2); // partner+relationship + assertThat(result.returnedValue()).isEqualTo(1); + assertThat(objectDisplaysOf(rawObjectRepo.findAll())).containsExactlyInAnyOrder(initialObjects); assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(initialRoleNames); assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(initialGrantNames); } @@ -439,7 +425,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean return jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); final var partnerRole = givenSomeTemporaryHostsharingPartnerRole(person, contact); - // em.flush(); // TODO: why is that necessary? final var newPartner = HsOfficePartnerEntity.builder() .partnerNumber(partnerNumber) @@ -480,9 +465,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean @AfterEach void cleanup() { - cleanupAllNew(HsOfficePartnerDetailsEntity.class); // TODO: should not be necessary cleanupAllNew(HsOfficePartnerEntity.class); - cleanupAllNew(HsOfficeRelationshipEntity.class); } private String[] distinct(final String[] strings) { diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsMermaidServiceIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsMermaidServiceIntegrationTest.java index 9a1a4f93..1afde73d 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsMermaidServiceIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsMermaidServiceIntegrationTest.java @@ -4,7 +4,6 @@ import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsMermaidService.Include; import net.hostsharing.test.JpaAttempt; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectEntity.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectEntity.java new file mode 100644 index 00000000..d4256e56 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectEntity.java @@ -0,0 +1,31 @@ +package net.hostsharing.hsadminng.rbac.rbacrole; + +import lombok.*; +import org.jetbrains.annotations.NotNull; +import org.springframework.data.annotation.Immutable; + +import jakarta.persistence.*; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "rbacobject") // TODO: create view rbacobject_ev +@Getter +@Setter +@ToString +@Immutable +@NoArgsConstructor +@AllArgsConstructor +public class RawRbacObjectEntity { + + @Id + private UUID uuid; + + @Column(name="objecttable") + private String objectTable; + + @NotNull + public static List objectDisplaysOf(@NotNull final List roles) { + return roles.stream().map(e -> e.objectTable+ "#" + e.uuid).sorted().toList(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectRepository.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectRepository.java new file mode 100644 index 00000000..ab645316 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectRepository.java @@ -0,0 +1,11 @@ +package net.hostsharing.hsadminng.rbac.rbacrole; + +import org.springframework.data.repository.Repository; + +import java.util.List; +import java.util.UUID; + +public interface RawRbacObjectRepository extends Repository { + + List findAll(); +}