credentials validation (#194)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/194 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
+13
-2
@@ -9,13 +9,24 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
class HsCredentialsContextRbacEntityUnitTest {
|
||||
|
||||
@Test
|
||||
void toShortString() {
|
||||
void toShortStringContainsJustTypeAndQualifier() {
|
||||
final var entity = HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.publicAccess(true)
|
||||
.build();
|
||||
assertEquals("loginContext(SSH:prod:PUBLIC)", entity.toShortString());
|
||||
assertEquals("SSH:prod", entity.toShortString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringContainsAllNonNullFields() {
|
||||
final var entity = HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.publicAccess(true)
|
||||
.build();
|
||||
assertEquals("loginContext(SSH:prod:PUBLIC)", entity.toString());
|
||||
}
|
||||
}
|
||||
|
||||
+16
-5
@@ -9,13 +9,24 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
class HsCredentialsContextRealEntityUnitTest {
|
||||
|
||||
@Test
|
||||
void toShortString() {
|
||||
void toShortStringContainsJustTypeAndQualifier() {
|
||||
final var entity = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("testType")
|
||||
.qualifier("testQualifier")
|
||||
.onlyForNaturalPersons(true)
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.publicAccess(true)
|
||||
.build();
|
||||
assertEquals("loginContext(testType:testQualifier:NP-ONLY:INTERNAL)", entity.toShortString());
|
||||
assertEquals("SSH:prod", entity.toShortString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringContainsAllNonNullFields() {
|
||||
final var entity = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.publicAccess(true)
|
||||
.build();
|
||||
assertEquals("loginContext(SSH:prod:PUBLIC)", entity.toString());
|
||||
}
|
||||
}
|
||||
|
||||
+107
-24
@@ -1,5 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.accounts;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -47,7 +48,7 @@ class HsCredentialsContextsControllerRestTest {
|
||||
Context contextMock;
|
||||
|
||||
@Autowired
|
||||
@SuppressWarnings("unused") // not used in test, but in controller class
|
||||
@SuppressWarnings("unused") // not used in test but in controller class
|
||||
StrictMapper mapper;
|
||||
|
||||
@MockitoBean
|
||||
@@ -59,15 +60,12 @@ class HsCredentialsContextsControllerRestTest {
|
||||
@MockitoBean
|
||||
HsCredentialsContextRbacRepository loginContextRbacRepo;
|
||||
|
||||
|
||||
@TestConfiguration
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public EntityManager entityManager() {
|
||||
return mock(EntityManager.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
@@ -82,18 +80,27 @@ class HsCredentialsContextsControllerRestTest {
|
||||
void getListOfLoginContextsReturnsOkWithEmptyList() throws Exception {
|
||||
|
||||
// given
|
||||
when(loginContextRbacRepo.findAll()).thenReturn(List.of(
|
||||
HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("HSADMIN")
|
||||
.qualifier("prod")
|
||||
.build(),
|
||||
HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.build()
|
||||
));
|
||||
givenNoContextsInTheRepository();
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.get("/api/hs/accounts/contexts")
|
||||
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
|
||||
// then
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$").isArray())
|
||||
.andExpect(jsonPath("$").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getListOfLoginContextsReturnsAllContextsForGlobalAdmin() throws Exception {
|
||||
|
||||
// given
|
||||
givenSomeContextsInTheRepository();
|
||||
when(contextMock.isGlobalAdmin()).thenReturn(true);
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
@@ -107,16 +114,92 @@ class HsCredentialsContextsControllerRestTest {
|
||||
.andExpect(jsonPath(
|
||||
"$", lenientlyEquals("""
|
||||
[
|
||||
{
|
||||
"type": "HSADMIN",
|
||||
"qualifier": "prod"
|
||||
},
|
||||
{
|
||||
"type": "SSH",
|
||||
"qualifier": "prod"
|
||||
}
|
||||
{
|
||||
"type": "HSADMIN",
|
||||
"qualifier": "prod",
|
||||
"onlyForNaturalPersons": true,
|
||||
"publicAccess": true
|
||||
},
|
||||
{
|
||||
"type": "SSH",
|
||||
"qualifier": "public",
|
||||
"onlyForNaturalPersons": false,
|
||||
"publicAccess": true
|
||||
},
|
||||
{
|
||||
"type": "SSH",
|
||||
"qualifier": "internal",
|
||||
"onlyForNaturalPersons": false,
|
||||
"publicAccess": false
|
||||
}
|
||||
]
|
||||
"""
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getListOfLoginContextsReturnsOnlyPublicContextsForNormalUser() throws Exception {
|
||||
|
||||
// given
|
||||
givenSomeContextsInTheRepository();
|
||||
when(contextMock.isGlobalAdmin()).thenReturn(false);
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.get("/api/hs/accounts/contexts")
|
||||
.header("Authorization", "Bearer drew@hostsharing.org")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
|
||||
// then
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath(
|
||||
"$", lenientlyEquals("""
|
||||
[
|
||||
{
|
||||
"type": "HSADMIN",
|
||||
"qualifier": "prod",
|
||||
"onlyForNaturalPersons": true,
|
||||
"publicAccess": true
|
||||
},
|
||||
{
|
||||
"type": "SSH",
|
||||
"qualifier": "public",
|
||||
"onlyForNaturalPersons": false,
|
||||
"publicAccess": true
|
||||
}
|
||||
]
|
||||
"""
|
||||
)));
|
||||
}
|
||||
|
||||
private void givenNoContextsInTheRepository() {
|
||||
when(loginContextRbacRepo.findAll()).thenReturn(emptyList());
|
||||
}
|
||||
|
||||
private void givenSomeContextsInTheRepository() {
|
||||
when(loginContextRbacRepo.findAll()).thenReturn(List.of(
|
||||
HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("HSADMIN")
|
||||
.qualifier("prod")
|
||||
.publicAccess(true)
|
||||
.onlyForNaturalPersons(true)
|
||||
.build(),
|
||||
HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("public")
|
||||
.publicAccess(true)
|
||||
.onlyForNaturalPersons(false)
|
||||
.build(),
|
||||
HsCredentialsContextRbacEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("internal")
|
||||
.publicAccess(false)
|
||||
.onlyForNaturalPersons(false)
|
||||
.build()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
package net.hostsharing.hsadminng.hs.accounts;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import lombok.val;
|
||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.accounts.HsCredentialsEntity.HsCredentialsEntityBuilder;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
|
||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL_PERSON;
|
||||
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.NATURAL_PERSON;
|
||||
import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
@Transactional
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { HsadminNgApplication.class, DisableSecurityConfig.class, JpaAttempt.class }
|
||||
)
|
||||
@ActiveProfiles("test")
|
||||
@Tag("generalIntegrationTest")
|
||||
// too complex database interaction for just a RestTest, thus a fully integrated test
|
||||
class HsCredentialsControllerAcceptanceTest extends ContextBasedTestWithCleanup {
|
||||
|
||||
@LocalServerPort
|
||||
private Integer port;
|
||||
|
||||
@Autowired
|
||||
Context context;
|
||||
|
||||
@Autowired
|
||||
RbacSubjectRepository subjectRepo;
|
||||
|
||||
@Autowired
|
||||
HsOfficePersonRealRepository realPersonRepo;
|
||||
|
||||
@Autowired
|
||||
HsCredentialsContextRealRepository contextRepo;
|
||||
|
||||
@Autowired
|
||||
HsCredentialsRepository credentialsRepo;
|
||||
|
||||
@Autowired
|
||||
HsCredentialsContextRbacRepository loginContextRbacRepo;
|
||||
|
||||
@Autowired
|
||||
JpaAttempt jpaAttempt;
|
||||
|
||||
@PersistenceContext
|
||||
EntityManager em;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
}
|
||||
|
||||
@Nested
|
||||
class GetCurrentUser {
|
||||
|
||||
@Test
|
||||
void shouldFetchCurrentLoginUser() throws Exception {
|
||||
// given
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
|
||||
.port(port)
|
||||
.when()
|
||||
.get("http://localhost/api/hs/accounts/current")
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(200)
|
||||
.contentType("application/json")
|
||||
.body("subject.name", equalTo("superuser-alex@hostsharing.net"))
|
||||
.body("globalAdmin", equalTo(true));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class GetCredentialsByUuid {
|
||||
|
||||
@Test
|
||||
void shouldFilterInvalidContextsRegardingNonNaturalPerson() {
|
||||
// given
|
||||
val legalPerson = givenLegalPerson("selfregistered-user-drew@hostsharing.org");
|
||||
val credentialsEntity = givenNewCredentials("selfregistered-user-drew@hostsharing.org",
|
||||
"test-subject1", legalPerson, builder -> {
|
||||
builder.loginContexts(new HashSet<>(contextRepo.findAll()));
|
||||
});
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer " + credentialsEntity.getSubject().getName())
|
||||
.port(port)
|
||||
.when()
|
||||
.get("http://localhost/api/hs/accounts/credentials/" + credentialsEntity.getUuid())
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(200)
|
||||
.contentType("application/json")
|
||||
.body("$", lenientlyEquals("""
|
||||
{
|
||||
"person": {
|
||||
"personType": "LEGAL_PERSON",
|
||||
"tradeName": "Test Company",
|
||||
"salutation": null,
|
||||
"title": null,
|
||||
"givenName": null,
|
||||
"familyName": null
|
||||
},
|
||||
"nickname": "test-subject1",
|
||||
"totpSecrets": null,
|
||||
"phonePassword": null,
|
||||
"emailAddress": null,
|
||||
"smsNumber": null,
|
||||
"active": false,
|
||||
"globalUid": null,
|
||||
"globalGid": null,
|
||||
"onboardingToken": null,
|
||||
"contexts": [
|
||||
{
|
||||
"uuid": "33333333-3333-3333-3333-333333333333",
|
||||
"type": "SSH",
|
||||
"qualifier": "external",
|
||||
"onlyForNaturalPersons": false,
|
||||
"publicAccess": true
|
||||
},
|
||||
{
|
||||
"uuid": "66666666-6666-6666-6666-666666666666",
|
||||
"type": "MASTODON",
|
||||
"qualifier": "external",
|
||||
"onlyForNaturalPersons": false,
|
||||
"publicAccess": true
|
||||
},
|
||||
{
|
||||
"uuid": "77777777-7777-7777-7777-777777777777",
|
||||
"type": "BBB",
|
||||
"qualifier": "external",
|
||||
"onlyForNaturalPersons": false,
|
||||
"publicAccess": true
|
||||
}
|
||||
],
|
||||
"lastUsed": null
|
||||
}
|
||||
"""));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PostNewCredentials {
|
||||
|
||||
@Test
|
||||
void shouldRejectCreatingCredentialsForUnrepresentedPerson() {
|
||||
// given
|
||||
val testPerson = givenPersonWithUuid("selfregistered-user-drew@hostsharing.org");
|
||||
val publicContext = contextRepo.findByTypeAndQualifier("SSH", "external").orElseThrow();
|
||||
assertThat(publicContext.isPublicAccess()).as("precondition failed").isTrue();
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer selfregistered-user-drew@hostsharing.org")
|
||||
.header("Accept-Language", "de")
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"person.uuid": "%s",
|
||||
"nickname": "new-user",
|
||||
"active": true,
|
||||
"globalUid": 30001,
|
||||
"globalGid": 40001,
|
||||
"contexts": [
|
||||
{
|
||||
"uuid" : "%s"
|
||||
}
|
||||
]
|
||||
}
|
||||
""".formatted(testPerson.getUuid(), publicContext.getUuid()))
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/hs/accounts/credentials")
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(400)
|
||||
.contentType("application/json")
|
||||
.body("message", containsString("wird von der eingeloggten Person nicht repräsentiert"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectCreatingCredentialsWithPrivateContextForNormalUser() {
|
||||
// given
|
||||
val drewPerson = realPersonRepo.findPersonByOptionalNameLike("Drew").getFirst();
|
||||
val privateInternalSshContext = contextRepo.findByTypeAndQualifier("SSH", "internal")
|
||||
.map(HsCredentialsControllerAcceptanceTest::asPrivateContext).orElseThrow();
|
||||
val privateInternalMatrixContext = contextRepo.findByTypeAndQualifier("MATRIX", "internal")
|
||||
.map(HsCredentialsControllerAcceptanceTest::asPrivateContext).orElseThrow();
|
||||
val publicExternalMatrixContext = contextRepo.findByTypeAndQualifier("MATRIX", "external")
|
||||
.map(HsCredentialsControllerAcceptanceTest::asPublicContext).orElseThrow();
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer selfregistered-user-drew@hostsharing.org")
|
||||
.header("Accept-Language", "de")
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"person.uuid": "%s",
|
||||
"nickname": "new-user",
|
||||
"active": true,
|
||||
"globalUid": 30001,
|
||||
"globalGid": 40001,
|
||||
"contexts": [
|
||||
{ "uuid" : "%s" },
|
||||
{ "uuid" : "%s" },
|
||||
{ "uuid" : "%s" }
|
||||
]
|
||||
}
|
||||
""".formatted(
|
||||
drewPerson.getUuid(),
|
||||
publicExternalMatrixContext.getUuid(),
|
||||
privateInternalSshContext.getUuid(),
|
||||
privateInternalMatrixContext.getUuid()))
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/hs/accounts/credentials")
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(400)
|
||||
.contentType("application/json")
|
||||
.body("message", containsString("Kontext-Zugriff verweigert: 'MATRIX:internal', 'SSH:internal'"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectCreatingCredentialsWithNaturalPersonRequirementForNonNaturalPerson() {
|
||||
// given
|
||||
val firstGmbHPerson = realPersonRepo.findPersonByOptionalNameLike("First").getFirst();
|
||||
val hsadminProdContextOnlyForNaturalPersons = contextRepo.findByTypeAndQualifier("HSADMIN", "prod")
|
||||
.map(HsCredentialsControllerAcceptanceTest::asNaturalPersonContext).orElseThrow();
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
|
||||
.header("Accept-Language", "de")
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"person.uuid": "%s",
|
||||
"nickname": "new-user",
|
||||
"active": true,
|
||||
"globalUid": 30001,
|
||||
"globalGid": 40001,
|
||||
"contexts": [
|
||||
{ "uuid" : "%s" }
|
||||
]
|
||||
}
|
||||
""".formatted(
|
||||
firstGmbHPerson.getUuid(),
|
||||
hsadminProdContextOnlyForNaturalPersons.getUuid()))
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/hs/accounts/credentials")
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(400)
|
||||
.contentType("application/json")
|
||||
.body("message", containsString("Kontext verlangt eine natürliche Person: 'HSADMIN:prod'"));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PatchCredentials {
|
||||
|
||||
@Test
|
||||
void shouldRejectPatchingCredentialsWithPrivateContextForNormalUser() {
|
||||
// given
|
||||
context.define("selfregistered-user-drew@hostsharing.org");
|
||||
val drewCredentialsUuid = credentialsRepo.findByCurrentSubject().stream().findFirst().orElseThrow()
|
||||
.getSubject().getUuid();
|
||||
val privateInternalSshContext = contextRepo.findByTypeAndQualifier("SSH", "internal")
|
||||
.map(HsCredentialsControllerAcceptanceTest::asPrivateContext).orElseThrow();
|
||||
val privateInternalMatrixContext = contextRepo.findByTypeAndQualifier("MATRIX", "internal")
|
||||
.map(HsCredentialsControllerAcceptanceTest::asPrivateContext).orElseThrow();
|
||||
val publicExternalMatrixContext = contextRepo.findByTypeAndQualifier("MATRIX", "external")
|
||||
.map(HsCredentialsControllerAcceptanceTest::asPublicContext).orElseThrow();
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer selfregistered-user-drew@hostsharing.org")
|
||||
.header("Accept-Language", "de")
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"contexts": [
|
||||
{ "uuid" : "%s" },
|
||||
{ "uuid" : "%s" },
|
||||
{ "uuid" : "%s" }
|
||||
]
|
||||
}
|
||||
""".formatted(
|
||||
privateInternalSshContext.getUuid(),
|
||||
publicExternalMatrixContext.getUuid(),
|
||||
privateInternalMatrixContext.getUuid()))
|
||||
.port(port)
|
||||
.when()
|
||||
.patch("http://localhost/api/hs/accounts/credentials/" + drewCredentialsUuid)
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(400)
|
||||
.contentType("application/json")
|
||||
.body("message", containsString("Kontext-Zugriff verweigert: 'MATRIX:internal', 'SSH:internal'"));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectPatchingCredentialsAndRemovingTheOwnHsadminCredentials() {
|
||||
// given
|
||||
context.define("selfregistered-user-drew@hostsharing.org");
|
||||
val drewCredentialsUuid = credentialsRepo.findByCurrentSubject().stream().findFirst().orElseThrow()
|
||||
.getSubject().getUuid();
|
||||
val publicExternalMatrixContext = contextRepo.findByTypeAndQualifier("MATRIX", "external")
|
||||
.map(HsCredentialsControllerAcceptanceTest::asPublicContext).orElseThrow();
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer selfregistered-user-drew@hostsharing.org")
|
||||
.header("Accept-Language", "de")
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"contexts": [
|
||||
{ "uuid" : "%s" }
|
||||
]
|
||||
}
|
||||
""".formatted(publicExternalMatrixContext.getUuid()))
|
||||
.port(port)
|
||||
.when()
|
||||
.patch("http://localhost/api/hs/accounts/credentials/" + drewCredentialsUuid)
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(400)
|
||||
.contentType("application/json")
|
||||
.body("message", containsString("die eigenen hsadmin-Credentials dürfen nicht entfernt werden"));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class MarkCredentialsAsUsed {
|
||||
|
||||
@Test
|
||||
void markCredentialsAsUsed() {
|
||||
// given
|
||||
val testPerson = givenNaturalPerson("selfregistered-user-drew@hostsharing.org");
|
||||
val credentialsEntity = givenNewCredentials("selfregistered-user-drew@hostsharing.org",
|
||||
"test-subject2",
|
||||
testPerson, builder -> {
|
||||
builder.onboardingToken("some-onboarding-token");
|
||||
builder.loginContexts(contextRepo.findAll().stream()
|
||||
.filter(HsCredentialsContext::isPublicAccess).collect(Collectors.toSet()));
|
||||
});
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/hs/accounts/credentials/" + credentialsEntity.getUuid() + "/used")
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(200)
|
||||
.contentType("application/json")
|
||||
.body("uuid", is(credentialsEntity.getUuid().toString()))
|
||||
.body("onboardingToken", is(nullValue()))
|
||||
.body("lastUsed", is(not(nullValue())));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
private HsOfficePersonRealEntity givenLegalPerson(final String executingSubjectName) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
context.define(executingSubjectName);
|
||||
return toCleanup(realPersonRepo.save(HsOfficePersonRealEntity.builder()
|
||||
.personType(LEGAL_PERSON)
|
||||
.tradeName("Test Company")
|
||||
.build()));
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
|
||||
private HsOfficePersonRealEntity givenNaturalPerson(final String executingSubjectName) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
context.define(executingSubjectName);
|
||||
return toCleanup(realPersonRepo.save(HsOfficePersonRealEntity.builder()
|
||||
.personType(NATURAL_PERSON)
|
||||
.familyName("Test")
|
||||
.givenName("User")
|
||||
.build()));
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
|
||||
private HsOfficePersonRealEntity givenPersonWithUuid(final String executingSubjectName) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
context.define(executingSubjectName);
|
||||
return toCleanup(realPersonRepo.save(HsOfficePersonRealEntity.builder()
|
||||
.personType(NATURAL_PERSON)
|
||||
.familyName("Test")
|
||||
.givenName("Person")
|
||||
.build()));
|
||||
}).returnedValue();
|
||||
}
|
||||
|
||||
private static HsCredentialsContextRealEntity asNaturalPersonContext(@NotNull HsCredentialsContextRealEntity context) {
|
||||
assertThat(context.isOnlyForNaturalPersons()).as("precondition failed").isTrue();
|
||||
return context;
|
||||
}
|
||||
|
||||
private static HsCredentialsContextRealEntity asPrivateContext(@NotNull HsCredentialsContextRealEntity context) {
|
||||
assertThat(context.isPublicAccess()).as("precondition failed").isFalse();
|
||||
return context;
|
||||
}
|
||||
|
||||
private static HsCredentialsContextRealEntity asPublicContext(@NotNull HsCredentialsContextRealEntity context) {
|
||||
assertThat(context.isPublicAccess()).as("precondition failed").isTrue();
|
||||
return context;
|
||||
}
|
||||
|
||||
private HsCredentialsEntity givenNewCredentials(
|
||||
final String executingSubjectName,
|
||||
final String newSubjectName, final HsOfficePersonRealEntity person,
|
||||
final Consumer<HsCredentialsEntityBuilder> modifier
|
||||
) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
context.define(executingSubjectName);
|
||||
final RbacSubjectEntity rbacSubjectEntity = RbacSubjectEntity.builder()
|
||||
.name(newSubjectName)
|
||||
.build();
|
||||
val subject = subjectRepo.create(rbacSubjectEntity);
|
||||
|
||||
context.define(subject.getName());
|
||||
val attachedPerson = em.find(HsOfficePersonRealEntity.class, person.getUuid());
|
||||
val credentialsBuilder = HsCredentialsEntity.builder()
|
||||
.person(attachedPerson)
|
||||
.subject(subjectRepo.findByUuid(subject.getUuid()))
|
||||
.loginContexts(Set.of());
|
||||
modifier.accept(credentialsBuilder);
|
||||
return toCleanup(credentialsRepo.save(credentialsBuilder.build()));
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
}
|
||||
-314
@@ -1,314 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.accounts;
|
||||
|
||||
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
|
||||
import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
|
||||
import net.hostsharing.hsadminng.config.MessageTranslator;
|
||||
import net.hostsharing.hsadminng.config.MessagesResourceConfig;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
|
||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository;
|
||||
import org.hamcrest.CustomMatcher;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL_PERSON;
|
||||
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.NATURAL_PERSON;
|
||||
import static net.hostsharing.hsadminng.test.JsonMatcher.lenientlyEquals;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@WebMvcTest(HsCredentialsController.class)
|
||||
@Import({
|
||||
StrictMapper.class,
|
||||
JsonObjectMapperConfiguration.class,
|
||||
DisableSecurityConfig.class,
|
||||
// HOWTO: test i18n translations
|
||||
MessagesResourceConfig.class,
|
||||
MessageTranslator.class })
|
||||
@ActiveProfiles("test")
|
||||
class HsCredentialsControllerRestTest {
|
||||
|
||||
private static final UUID PERSON_UUID = UUID.randomUUID();
|
||||
|
||||
@Autowired
|
||||
MockMvc mockMvc;
|
||||
|
||||
@MockitoBean
|
||||
Context contextMock;
|
||||
|
||||
@Autowired
|
||||
@SuppressWarnings("unused") // not used in test, but in controller class
|
||||
StrictMapper mapper;
|
||||
|
||||
@MockitoBean
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@MockitoBean
|
||||
EntityManagerFactory emf;
|
||||
|
||||
@MockitoBean
|
||||
RbacSubjectRepository subjectRepo;
|
||||
|
||||
@MockitoBean
|
||||
HsOfficePersonRealRepository realPersonRepo;
|
||||
|
||||
@MockitoBean
|
||||
HsOfficePersonRbacRepository rbacPersonRepo;
|
||||
|
||||
@MockitoBean
|
||||
HsCredentialsContextRbacRepository loginContextRbacRepo;
|
||||
|
||||
@MockitoBean
|
||||
HsCredentialsRepository credentialsRepo;
|
||||
|
||||
@MockitoBean
|
||||
CredentialContextResourceToEntityMapper contextMapper;
|
||||
|
||||
@Test
|
||||
void shouldFetchCurrentLoginUser() throws Exception {
|
||||
// given
|
||||
final UUID currentSubjectUuid = UUID.randomUUID();
|
||||
given(contextMock.fetchCurrentSubjectUuid()).willReturn(currentSubjectUuid);
|
||||
given(contextMock.isGlobalAdmin()).willReturn(true);
|
||||
given(subjectRepo.findByUuid(currentSubjectUuid)).willReturn(
|
||||
RbacSubjectEntity.builder().uuid(currentSubjectUuid).name("test-user").build()
|
||||
);
|
||||
given(credentialsRepo.findByUuid(currentSubjectUuid)).willReturn(
|
||||
Optional.of(HsCredentialsEntity.builder()
|
||||
.uuid(currentSubjectUuid)
|
||||
.person(HsOfficePersonRbacEntity.builder()
|
||||
.uuid(PERSON_UUID)
|
||||
.personType(NATURAL_PERSON)
|
||||
.familyName("Miller")
|
||||
.givenName("Steph")
|
||||
.build())
|
||||
.subject(RbacSubjectEntity.builder().name("steph-miller").build())
|
||||
.build())
|
||||
);
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.get("/api/hs/accounts/current")
|
||||
.header("Authorization", "Bearer test")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
|
||||
// then
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.subject.uuid").value(currentSubjectUuid.toString()))
|
||||
.andExpect(jsonPath("$.subject.name").value("test-user"))
|
||||
.andExpect(jsonPath("$.person.uuid").value(PERSON_UUID.toString()))
|
||||
.andExpect(jsonPath("$.person.familyName").value("Miller"))
|
||||
.andExpect(jsonPath("$.person.givenName").value("Steph"))
|
||||
.andExpect(jsonPath("$.globalAdmin").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFilterInvalidContextsRegardingNonNaturalPerson() throws Exception {
|
||||
// given
|
||||
final var givenCredentialsUuid = UUID.randomUUID();
|
||||
final var contextForNP = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("HSADMIN")
|
||||
.qualifier("prod")
|
||||
.onlyForNaturalPersons(true)
|
||||
.build();
|
||||
final var contextForAll = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.onlyForNaturalPersons(false)
|
||||
.build();
|
||||
final var credentialsEntity = HsCredentialsEntity.builder()
|
||||
.uuid(givenCredentialsUuid)
|
||||
.person(HsOfficePersonRbacEntity.builder()
|
||||
.uuid(PERSON_UUID)
|
||||
.personType(LEGAL_PERSON)
|
||||
.build())
|
||||
.subject(RbacSubjectEntity.builder().name("some-nickname").build())
|
||||
.loginContexts(Set.of(contextForNP, contextForAll))
|
||||
.build();
|
||||
when(credentialsRepo.findByUuid(givenCredentialsUuid))
|
||||
.thenReturn(Optional.of(credentialsEntity));
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.get("/api/hs/accounts/credentials/" + givenCredentialsUuid)
|
||||
.header("Authorization", "Bearer test")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
|
||||
// then
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.contexts.length()").value(1))
|
||||
.andExpect(jsonPath("$.contexts[0].type").value("SSH"))
|
||||
.andExpect(jsonPath("$.contexts[0].qualifier").value("prod"))
|
||||
.andExpect(jsonPath("$.contexts[0].onlyForNaturalPersons").value(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectCreatingCredentialsForUnrepresentedPerson() throws Exception {
|
||||
// given
|
||||
final var personUuid = UUID.randomUUID();
|
||||
|
||||
final AtomicReference<RbacSubjectEntity> createdSubject = new AtomicReference<>();
|
||||
given(subjectRepo.create(any())).willAnswer(invocation -> {
|
||||
final var passedEntity = (RbacSubjectEntity) invocation.getArgument(0);
|
||||
passedEntity.setUuid(UUID.randomUUID());
|
||||
createdSubject.set(passedEntity); // Capture the instance
|
||||
return passedEntity;
|
||||
});
|
||||
given(contextMock.fetchCurrentSubject()).willAnswer(invocation -> createdSubject.get().getName());
|
||||
given(subjectRepo.findByUuid(any())).willAnswer(invocation -> createdSubject.get());
|
||||
given(rbacPersonRepo.findByUuid(personUuid)).willReturn(Optional.of(
|
||||
HsOfficePersonRbacEntity.builder().uuid(personUuid).personType(NATURAL_PERSON).build()
|
||||
));
|
||||
given(rbacPersonRepo.findPersonsRepresentedByPersonWithUuid(personUuid)).willReturn(List.of(
|
||||
// some persons, but not the one from the login-user itself
|
||||
HsOfficePersonRbacEntity.builder().uuid(UUID.randomUUID()).personType(NATURAL_PERSON).build(),
|
||||
HsOfficePersonRbacEntity.builder().uuid(UUID.randomUUID()).personType(LEGAL_PERSON).build()
|
||||
));
|
||||
|
||||
final var givenCredentialsUuid = UUID.randomUUID();
|
||||
|
||||
final var contextForNP = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("HSADMIN")
|
||||
.qualifier("prod")
|
||||
.onlyForNaturalPersons(true)
|
||||
.build();
|
||||
final var contextForAll = HsCredentialsContextRealEntity.builder()
|
||||
.uuid(UUID.randomUUID())
|
||||
.type("SSH")
|
||||
.qualifier("prod")
|
||||
.onlyForNaturalPersons(false)
|
||||
.build();
|
||||
final var credentialsEntity = HsCredentialsEntity.builder()
|
||||
.uuid(givenCredentialsUuid)
|
||||
.person(HsOfficePersonRbacEntity.builder()
|
||||
.uuid(PERSON_UUID)
|
||||
.personType(LEGAL_PERSON)
|
||||
.build())
|
||||
.subject(RbacSubjectEntity.builder().name("some-nickname").build())
|
||||
.loginContexts(Set.of(contextForNP, contextForAll))
|
||||
.build();
|
||||
when(credentialsRepo.findByUuid(givenCredentialsUuid))
|
||||
.thenReturn(Optional.of(credentialsEntity));
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.post("/api/hs/accounts/credentials")
|
||||
.header("Authorization", "Bearer test")
|
||||
// HOWTO: test i18n translations
|
||||
.header("Accept-Language", "de")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(
|
||||
"""
|
||||
{
|
||||
"person.uuid": "${personUuid}",
|
||||
"nickname": "${nickname}",
|
||||
"active": true,
|
||||
"globalUid": 30001,
|
||||
"globalGid": 40001,
|
||||
"contexts": [
|
||||
{
|
||||
"uuid" : "11111111-1111-1111-1111-111111111111",
|
||||
"type" : "HSADMIN",
|
||||
"qualifier" : "prod",
|
||||
"onlyForNaturalPersons" : true,
|
||||
"publicAccess" : true
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
.replace("${personUuid}", personUuid.toString())
|
||||
.replace("${nickname}", "new-user")
|
||||
)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
|
||||
// then
|
||||
.andExpect(status().is4xxClientError())
|
||||
.andExpect(jsonPath("$.message", containsString(
|
||||
"Zugriff verweigert: personUuid \"${personUuid}\" wird von der eingeloggten Person nicht repräsentiert"
|
||||
.replace("${personUuid}", personUuid.toString()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void markCredentialsAsUsed() throws Exception {
|
||||
|
||||
// given
|
||||
final var givenCredentialsUuid = UUID.randomUUID();
|
||||
when(credentialsRepo.findByUuid(givenCredentialsUuid)).thenReturn(Optional.of(
|
||||
HsCredentialsEntity.builder()
|
||||
.uuid(givenCredentialsUuid)
|
||||
.person(HsOfficePersonRbacEntity.builder().uuid(PERSON_UUID).build())
|
||||
.subject(RbacSubjectEntity.builder().name("some-nickname").build())
|
||||
.lastUsed(null)
|
||||
.onboardingToken("fake-onboarding-token")
|
||||
.build()
|
||||
));
|
||||
when(credentialsRepo.save(any())).thenAnswer(invocation ->
|
||||
invocation.getArgument(0)
|
||||
);
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.post("/api/hs/accounts/credentials/%{credentialsUuid}/used"
|
||||
.replace("%{credentialsUuid}", givenCredentialsUuid.toString()))
|
||||
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
|
||||
// then
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath(
|
||||
"$", lenientlyEquals("""
|
||||
{
|
||||
"uuid": "%{credentialsUuid}",
|
||||
"onboardingToken": null
|
||||
}
|
||||
""".replace("%{credentialsUuid}", givenCredentialsUuid.toString())
|
||||
)))
|
||||
.andExpect(jsonPath("$.lastUsed").value(new CustomMatcher<String>("lastUsed should have recent timestamp") {
|
||||
|
||||
@Override
|
||||
public boolean matches(final Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
final var lastUsed = ZonedDateTime.parse(o.toString(), DateTimeFormatter.ISO_DATE_TIME)
|
||||
.toLocalDateTime();
|
||||
return lastUsed.isAfter(LocalDateTime.now().minusMinutes(1)) &&
|
||||
lastUsed.isBefore(LocalDateTime.now());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.hs.accounts;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
@@ -62,8 +62,8 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
||||
private RbacSubjectEntity alexSubject;
|
||||
private RbacSubjectEntity drewSubject;
|
||||
private RbacSubjectEntity testUserSubject;
|
||||
private HsOfficePersonRbacEntity drewPerson;
|
||||
private HsOfficePersonRbacEntity testUserPerson;
|
||||
private HsOfficePersonRealEntity drewPerson;
|
||||
private HsOfficePersonRealEntity testUserPerson;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -277,13 +277,13 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
||||
}
|
||||
}
|
||||
|
||||
private HsOfficePersonRbacEntity fetchPersonByGivenName(final String givenName) {
|
||||
final String jpql = "SELECT p FROM HsOfficePersonRbacEntity p WHERE p.givenName = :givenName";
|
||||
final Query query = em.createQuery(jpql, HsOfficePersonRbacEntity.class);
|
||||
private HsOfficePersonRealEntity fetchPersonByGivenName(final String givenName) {
|
||||
final String jpql = "SELECT p FROM HsOfficePersonRealEntity p WHERE p.givenName = :givenName";
|
||||
final Query query = em.createQuery(jpql, HsOfficePersonRealEntity.class);
|
||||
query.setParameter("givenName", givenName);
|
||||
try {
|
||||
context(SUPERUSER_ALEX_SUBJECT_NAME);
|
||||
return notNull((HsOfficePersonRbacEntity) query.getSingleResult());
|
||||
return notNull((HsOfficePersonRealEntity) query.getSingleResult());
|
||||
} catch (final NoResultException e) {
|
||||
throw new AssertionError(
|
||||
"Failed to find person with name '" + givenName + "'. Ensure test data is present.", e);
|
||||
@@ -315,7 +315,7 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
||||
private class RelationBuilder {
|
||||
private final HsOfficeRelationType relationType;
|
||||
private HsOfficePersonRealEntity anchorPerson;
|
||||
private HsOfficePersonRbacEntity holderPerson;
|
||||
private HsOfficePersonRealEntity holderPerson;
|
||||
private HsOfficeContactRealEntity contact;
|
||||
|
||||
public RelationBuilder(HsOfficeRelationType relationType) {
|
||||
@@ -327,7 +327,7 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationBuilder withHolder(HsOfficePersonRbacEntity holderPerson) {
|
||||
public RelationBuilder withHolder(HsOfficePersonRealEntity holderPerson) {
|
||||
this.holderPerson = holderPerson;
|
||||
return this;
|
||||
}
|
||||
@@ -373,7 +373,7 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
||||
final var credentials = HsCredentialsEntity.builder()
|
||||
.uuid(subject.getUuid())
|
||||
.subject(subject)
|
||||
.person(em.find(HsOfficePersonRbacEntity.class, person.getUuid()))
|
||||
.person(em.find(HsOfficePersonRealEntity.class, person.getUuid()))
|
||||
.emailAddress(emailAddress)
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
+34
-1
@@ -150,6 +150,39 @@ class HsOfficePersonRealRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findPersonsRepresentedByPersonWithUuid() {
|
||||
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var personUuid = personRealRepo.findPersonByOptionalNameLike("Fouler").getFirst().getUuid();
|
||||
|
||||
// when
|
||||
@SuppressWarnings("unchecked") final List<HsOfficePersonRealEntity> representedPersons = personRealRepo.findPersonsRepresentedByPersonWithUuid(personUuid);
|
||||
|
||||
// then
|
||||
assertThat(representedPersons).map(Object::toString).containsExactlyInAnyOrder(
|
||||
"person(personType=NP, familyName='Fouler', givenName='Ellie')",
|
||||
"person(personType=LP, tradeName='Fourth eG')"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findPersonsRepresentedByPersonWithUuidDrew() {
|
||||
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var personUuid = personRealRepo.findPersonByOptionalNameLike("Drew").getFirst().getUuid();
|
||||
|
||||
// when
|
||||
@SuppressWarnings("unchecked") final List<HsOfficePersonRealEntity> representedPersons = personRealRepo.findPersonsRepresentedByPersonWithUuid(personUuid);
|
||||
|
||||
// then
|
||||
assertThat(representedPersons).map(Object::toString).containsExactlyInAnyOrder(
|
||||
"person(personType=NP, familyName='User', givenName='Drew')"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void auditJournalLogIsAvailable() {
|
||||
// given
|
||||
@@ -157,7 +190,7 @@ class HsOfficePersonRealRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
select currentTask, targetTable, targetOp, targetdelta->>'tradename', targetdelta->>'lastname'
|
||||
from base.tx_journal_v
|
||||
where targettable = 'hs_office.person';
|
||||
""");
|
||||
""");
|
||||
|
||||
// when
|
||||
@SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList();
|
||||
|
||||
@@ -10,7 +10,6 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
public class EntityManagerWrapperFake extends EntityManagerWrapper {
|
||||
|
||||
private Map<Class<?>, Map<Object, Object>> entityClasses = new HashMap<>();
|
||||
|
||||
+9
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
@@ -19,7 +20,9 @@ import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
@@ -42,6 +45,12 @@ class RbacSubjectControllerRestTest {
|
||||
@MockitoBean
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
given(rbacSubjectRepository.create(any())).willAnswer(invocation ->
|
||||
invocation.<RbacSubjectEntity>getArgument(0)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postNewSubjectUsesGivenUuid() throws Exception {
|
||||
|
||||
@@ -53,7 +53,7 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
|
||||
@Autowired
|
||||
JpaAttempt jpaAttempt;
|
||||
|
||||
private TreeMap<UUID, Class<? extends ImmutableBaseEntity>> entitiesToCleanup = new TreeMap<>();
|
||||
private LinkedHashMap<UUID, Class<? extends ImmutableBaseEntity>> entitiesToCleanup = new LinkedHashMap<>();
|
||||
|
||||
private static Long latestIntialTestDataSerialId;
|
||||
private static boolean countersInitialized = false;
|
||||
@@ -102,6 +102,10 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
|
||||
? tableName.substring(0, tableName.length() - "_rv".length())
|
||||
: tableName;
|
||||
|
||||
final var rawTableName = rvTableName.endsWith("_rv")
|
||||
? rvTableName.substring(0, rvTableName.length() - "_rv".length())
|
||||
: rvTableName;
|
||||
|
||||
allRbacObjects().stream()
|
||||
.filter(o -> o.startsWith(rvTableName + ":"))
|
||||
.filter(o -> !initialRbacObjects.contains(o))
|
||||
@@ -191,11 +195,11 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
|
||||
context.define("superuser-alex@hostsharing.net", null);
|
||||
entitiesToCleanup.reversed().forEach((uuid, entityClass) -> {
|
||||
final var rvTableName = entityClass.getAnnotation(Table.class).name();
|
||||
if ( !rvTableName.endsWith("_rv") ) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
final var rawTableName = rvTableName.substring(0, rvTableName.length() - "_rv".length());
|
||||
final var deletedRows = em.createNativeQuery("DELETE FROM " + rawTableName + " WHERE uuid=:uuid")
|
||||
final var scope = entityClass.getAnnotation(Table.class).schema();
|
||||
final var rawTableName = rvTableName.endsWith("_rv")
|
||||
? rvTableName.substring(0, rvTableName.length() - "_rv".length())
|
||||
: rvTableName;
|
||||
final var deletedRows = em.createNativeQuery("DELETE FROM " + scope + "." + rawTableName + " WHERE uuid=:uuid")
|
||||
.setParameter("uuid", uuid).executeUpdate();
|
||||
out.println("DELETING temporary " + entityClass.getSimpleName() + "#" + uuid + " deleted " + deletedRows + " rows");
|
||||
});
|
||||
@@ -264,6 +268,9 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
|
||||
assertThat(after).isNotNull();
|
||||
final SetUtils.SetView<String> difference = difference(before, after);
|
||||
assertThat(difference).as("missing entities (deleted initial test data)").isEmpty();
|
||||
difference(after, before).stream().iterator().forEachRemaining(e -> {
|
||||
em.remove(e);
|
||||
});
|
||||
assertThat(difference(after, before)).as("spurious entities (test data not cleaned up by this test)").isEmpty();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user