all direct partner tests green

This commit is contained in:
Michael Hoennig 2024-02-19 13:19:57 +01:00
parent 717bdca948
commit 6a01002a05
13 changed files with 190 additions and 147 deletions

View File

@ -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.

View File

@ -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();
}

View File

@ -20,7 +20,7 @@ class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatch
@Override
public void apply(final HsOfficePartnerPatchResource resource) {
OptionalFromJson.of(resource.getPartnerRoleUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "contact");
verifyNotNull(newValue, "partnerRole");
entity.setPartnerRole(em.getReference(HsOfficeRelationshipEntity.class, newValue));
});

View File

@ -11,11 +11,13 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
Optional<HsOfficePartnerEntity> findByUuid(UUID id);
List<HsOfficePartnerEntity> 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), '%')

View File

@ -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> include) {
public String allGrantsToCurrentUser(final EnumSet<Include> includes) {
final var graph = new HashSet<RawRbacGrantEntity>();
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<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> include) {
private void traverseGrantsTo(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> 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> include) {
public String allGrantsFrom(final UUID targetObject, final String op, final EnumSet<Include> 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<RawRbacGrantEntity>();
traverseGrantsFrom(graph, refUuid, include);
return toMermaidFlowchart(graph);
traverseGrantsFrom(graph, refUuid, includes);
return toMermaidFlowchart(graph, includes);
}
private void traverseGrantsFrom(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> include) {
private void traverseGrantsFrom(final Set<RawRbacGrantEntity> graph, final UUID refUuid, final EnumSet<Include> 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<RawRbacGrantEntity> 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<RawRbacGrantEntity> graph, final EnumSet<Include> 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")) {

View File

@ -50,7 +50,7 @@ components:
HsOfficePartnerPatch:
type: object
properties:
partnerRoleUUid:
partnerRoleUuid:
type: string
format: uuid
nullable: true

View File

@ -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)

View File

@ -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());
}
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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;

View File

@ -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<String> objectDisplaysOf(@NotNull final List<RawRbacObjectEntity> roles) {
return roles.stream().map(e -> e.objectTable+ "#" + e.uuid).sorted().toList();
}
}

View File

@ -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<RawRbacObjectEntity, UUID> {
List<RawRbacObjectEntity> findAll();
}