move Parter+Debitor person+contact to related Relationsship #20
@ -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.
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
});
|
||||
|
||||
|
@ -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), '%')
|
||||
|
@ -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()
|
||||
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::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 "
|
||||
.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 "))
|
||||
.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()
|
||||
: "";
|
||||
|
||||
final var grants = graph.stream()
|
||||
.map(g -> quoted(g.getAscendantIdName()) +
|
||||
(g.isAssumed() ? " --> " : " -.-> ") +
|
||||
quoted(g.getDescendantIdName()))
|
||||
.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")) {
|
||||
|
@ -50,7 +50,7 @@ components:
|
||||
HsOfficePartnerPatch:
|
||||
type: object
|
||||
properties:
|
||||
partnerRoleUUid:
|
||||
partnerRoleUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
|
@ -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" },
|
||||
"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,9 +296,11 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
.contentType("application/json")
|
||||
.body("", lenientlyEquals("""
|
||||
{
|
||||
"person": { "tradeName": "First GmbH" },
|
||||
"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)
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user