add /api/rbac/context + /api/hs/accounts/current endpoints (#189)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/189 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
@@ -83,6 +83,7 @@ public class ArchitectureTest {
|
||||
"..mapper",
|
||||
"..ping",
|
||||
"..rbac",
|
||||
"..rbac.context",
|
||||
"..rbac.generator",
|
||||
"..rbac.subject",
|
||||
"..rbac.grant",
|
||||
@@ -238,7 +239,8 @@ public class ArchitectureTest {
|
||||
"..hs.office.debitor..",
|
||||
"..hs.office.membership..",
|
||||
"..hs.migration..",
|
||||
"..hs.hosting.asset.."
|
||||
"..hs.hosting.asset..",
|
||||
"..hs.accounts.."
|
||||
);
|
||||
|
||||
@ArchTest
|
||||
|
||||
+141
-2
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -26,20 +27,31 @@ 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, MessageTranslator.class })
|
||||
@Import({
|
||||
StrictMapper.class,
|
||||
JsonObjectMapperConfiguration.class,
|
||||
DisableSecurityConfig.class,
|
||||
// HOWTO: test i18n translations
|
||||
MessagesResourceConfig.class,
|
||||
MessageTranslator.class })
|
||||
@ActiveProfiles("test")
|
||||
class HsCredentialsControllerRestTest {
|
||||
|
||||
@@ -79,6 +91,45 @@ class HsCredentialsControllerRestTest {
|
||||
@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
|
||||
@@ -123,7 +174,95 @@ class HsCredentialsControllerRestTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void patchCredentialsUsed() throws Exception {
|
||||
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();
|
||||
|
||||
+156
-13
@@ -1,9 +1,14 @@
|
||||
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.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
|
||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.hibernate.TransientObjectException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -22,15 +27,18 @@ import java.time.ZonedDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.REPRESENTATIVE;
|
||||
import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
|
||||
@DataJpaTest
|
||||
@Tag("generalIntegrationTest")
|
||||
@Import({ Context.class, JpaAttempt.class })
|
||||
class HsCredentialsRepositoryIntegrationTest extends ContextBasedTest {
|
||||
class HsCredentialsRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
|
||||
|
||||
private static final String SUPERUSER_ALEX_SUBJECT_NAME = "superuser-alex@hostsharing.net";
|
||||
private static final String SUPERUSER_FRAN_SUBJECT_NAME = "superuser-fran@hostsharing.net";
|
||||
private static final String USER_DREW_SUBJECT_NAME = "selfregistered-user-drew@hostsharing.org";
|
||||
private static final String TEST_USER_SUBJECT_NAME = "selfregistered-test-user@hostsharing.org";
|
||||
|
||||
@@ -41,6 +49,9 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTest {
|
||||
@MockitoBean
|
||||
HttpServletRequest request;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePersonRealRepository personRepo;
|
||||
|
||||
@Autowired
|
||||
private HsCredentialsRepository credentialsRepository;
|
||||
|
||||
@@ -74,7 +85,9 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTest {
|
||||
final var rowsBefore = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(rowsBefore).as("hs_accounts.credentials_hv only contain no rows for a timestamp before test data creation").hasSize(0);
|
||||
assertThat(rowsBefore)
|
||||
.as("hs_accounts.credentials_hv only contain no rows for a timestamp before test data creation")
|
||||
.hasSize(0);
|
||||
|
||||
// and when
|
||||
historicalContext(Timestamp.from(ZonedDateTime.now().toInstant()));
|
||||
@@ -82,7 +95,53 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTest {
|
||||
final var rowsAfter = query.getResultList();
|
||||
|
||||
// then
|
||||
assertThat(rowsAfter).as("hs_accounts.credentials_hv should now contain the test-data rows for the current timestamp").hasSize(2);
|
||||
assertThat(rowsAfter)
|
||||
.as("hs_accounts.credentials_hv should now contain the test-data rows for the current timestamp")
|
||||
.hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void representativeShouldFindOwnAndRepresentedCredentialsByCurrentSubject() {
|
||||
// given
|
||||
final var firstGmbHPerson = givenPerson("First GmbH");
|
||||
givenRelation(REPRESENTATIVE)
|
||||
.withAnchorPersonLike(firstGmbHPerson)
|
||||
.withHolder(drewPerson)
|
||||
.withContact("some test contact");
|
||||
givenCredentials()
|
||||
.forSubject("first-gmbh")
|
||||
.forPerson(firstGmbHPerson)
|
||||
.withEMailAddress("first-gmbh@example.com");
|
||||
|
||||
// when
|
||||
final var foundCredentials = attempt(
|
||||
em, () -> {
|
||||
context(USER_DREW_SUBJECT_NAME);
|
||||
return credentialsRepository.findByCurrentSubject();
|
||||
})
|
||||
.assertNotNull().returnedValue();
|
||||
|
||||
// then
|
||||
assertThat(foundCredentials).hasSize(2)
|
||||
.map(HsCredentialsEntity::getEmailAddress)
|
||||
.containsExactlyInAnyOrder("drew@example.org", "first-gmbh@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void globalAdminShouldFindOnlyOwnCredentialsByCurrentSubject() {
|
||||
|
||||
// when
|
||||
final var foundCredentials = attempt(
|
||||
em, () -> {
|
||||
context(SUPERUSER_FRAN_SUBJECT_NAME);
|
||||
return credentialsRepository.findByCurrentSubject();
|
||||
})
|
||||
.assertNotNull().returnedValue();
|
||||
|
||||
// then
|
||||
assertThat(foundCredentials).hasSize(1)
|
||||
.map(HsCredentialsEntity::getEmailAddress)
|
||||
.containsExactlyInAnyOrder("fran@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -101,28 +160,28 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTest {
|
||||
final var existingContext = loginContextRealRepo.findByTypeAndQualifier("HSADMIN", "prod")
|
||||
.orElseThrow();
|
||||
final var newCredentials = HsCredentialsEntity.builder()
|
||||
.subject(drewSubject)
|
||||
.person(drewPerson)
|
||||
.subject(testUserSubject)
|
||||
.person(testUserPerson)
|
||||
.active(true)
|
||||
.emailAddress("drew.new@example.com")
|
||||
.globalUid(2001)
|
||||
.globalGid(2001)
|
||||
.emailAddress("test-user@example.com")
|
||||
.globalUid(2011)
|
||||
.globalGid(2011)
|
||||
.loginContexts(mutableSetOf(existingContext))
|
||||
.build();
|
||||
|
||||
// when
|
||||
credentialsRepository.save(newCredentials);
|
||||
toCleanup(credentialsRepository.save(newCredentials));
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// then
|
||||
final var foundEntityOptional = credentialsRepository.findByUuid(drewSubject.getUuid());
|
||||
final var foundEntityOptional = credentialsRepository.findByUuid(testUserSubject.getUuid());
|
||||
assertThat(foundEntityOptional).isPresent();
|
||||
final var foundEntity = foundEntityOptional.get();
|
||||
assertThat(foundEntity.getEmailAddress()).isEqualTo("drew.new@example.com");
|
||||
assertThat(foundEntity.getEmailAddress()).isEqualTo("test-user@example.com");
|
||||
assertThat(foundEntity.isActive()).isTrue();
|
||||
assertThat(foundEntity.getVersion()).isEqualTo(0); // Initial version
|
||||
assertThat(foundEntity.getGlobalUid()).isEqualTo(2001);
|
||||
assertThat(foundEntity.getGlobalUid()).isEqualTo(2011);
|
||||
|
||||
assertThat(foundEntity.getLoginContexts()).hasSize(1)
|
||||
.map(HsCredentialsContextRealEntity::toString).contains("loginContext(HSADMIN:prod:NP-ONLY:PUBLIC)");
|
||||
@@ -240,4 +299,88 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTest {
|
||||
private <T> Set<T> mutableSetOf(final T... elements) {
|
||||
return new HashSet<T>(Set.of(elements));
|
||||
}
|
||||
|
||||
private HsOfficePersonRealEntity givenPerson(String personName) {
|
||||
return personRepo.findPersonByOptionalNameLike(personName).getFirst();
|
||||
}
|
||||
|
||||
private RelationBuilder givenRelation(HsOfficeRelationType relationType) {
|
||||
return new RelationBuilder(relationType);
|
||||
}
|
||||
|
||||
private CredentialsBuilder givenCredentials() {
|
||||
return new CredentialsBuilder();
|
||||
}
|
||||
|
||||
private class RelationBuilder {
|
||||
private final HsOfficeRelationType relationType;
|
||||
private HsOfficePersonRealEntity anchorPerson;
|
||||
private HsOfficePersonRbacEntity holderPerson;
|
||||
private HsOfficeContactRealEntity contact;
|
||||
|
||||
public RelationBuilder(HsOfficeRelationType relationType) {
|
||||
this.relationType = relationType;
|
||||
}
|
||||
|
||||
public RelationBuilder withAnchorPersonLike(HsOfficePersonRealEntity anchorPerson) {
|
||||
this.anchorPerson = anchorPerson;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationBuilder withHolder(HsOfficePersonRbacEntity holderPerson) {
|
||||
this.holderPerson = holderPerson;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HsOfficeRelationRealEntity withContact(String caption) {
|
||||
this.contact = HsOfficeContactRealEntity.builder()
|
||||
.caption(caption)
|
||||
.build();
|
||||
em.persist(contact);
|
||||
|
||||
final var relation = HsOfficeRelationRealEntity.builder()
|
||||
.type(relationType)
|
||||
.anchor(anchorPerson)
|
||||
.holder(em.getReference(HsOfficePersonRealEntity.class, holderPerson.getUuid()))
|
||||
.contact(contact)
|
||||
.build();
|
||||
em.persist(relation);
|
||||
em.flush();
|
||||
return relation;
|
||||
}
|
||||
}
|
||||
|
||||
private class CredentialsBuilder {
|
||||
private RbacSubjectEntity subject;
|
||||
private HsOfficePersonRealEntity person;
|
||||
|
||||
public CredentialsBuilder forSubject(String subjectName) {
|
||||
this.subject = RbacSubjectEntity.builder()
|
||||
.name(subjectName)
|
||||
.build();
|
||||
em.persist(subject);
|
||||
toCleanup(subject);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CredentialsBuilder forPerson(HsOfficePersonRealEntity person) {
|
||||
this.person = person;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HsCredentialsEntity withEMailAddress(String emailAddress) {
|
||||
|
||||
final var credentials = HsCredentialsEntity.builder()
|
||||
.uuid(subject.getUuid())
|
||||
.subject(subject)
|
||||
.person(em.find(HsOfficePersonRbacEntity.class, person.getUuid()))
|
||||
.emailAddress(emailAddress)
|
||||
.active(true)
|
||||
.build();
|
||||
em.persist(credentials);
|
||||
toCleanup(credentials);
|
||||
em.flush();
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -49,6 +49,42 @@ class CredentialsScenarioTests extends ScenarioTest {
|
||||
@Nested
|
||||
@Order(10)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class RbacContextScenarios {
|
||||
|
||||
@Test
|
||||
@Order(1010)
|
||||
@Produces("RBAC Context")
|
||||
void shouldFetchRbacContext() {
|
||||
new FetchRbacContext(scenarioTest)
|
||||
.given("subjectName", "superuser-fran@hostsharing.net")
|
||||
.given("assumedRoles", "rbactest.package#xxx00:ADMIN;rbactest.package#yyy00:ADMIN")
|
||||
.given("expectedToBeGlobalAdmin", true)
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@Order(20)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class CurrentLoginUserScenarios {
|
||||
|
||||
@Test
|
||||
@Order(2010)
|
||||
@Produces("Current Login User")
|
||||
void shouldFetchCurrentLoginUser() {
|
||||
new CurrentLoginUser(scenarioTest)
|
||||
.given("subjectName", "superuser-fran@hostsharing.net")
|
||||
.given("personGivenName", "Fran")
|
||||
.given("expectedToBeGlobalAdmin", true)
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@Order(30)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class CredentialScenarios {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.hostsharing.hsadminng.hs.accounts.scenarios;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.scenarios.UseCase;
|
||||
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.resolve;
|
||||
import static net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class CurrentLoginUser extends UseCase<CurrentLoginUser> {
|
||||
|
||||
public CurrentLoginUser(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
introduction("Fetches data about the current login user.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Person: %{personGivenName}", () ->
|
||||
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{personGivenName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
return obtain(
|
||||
"Current Login User", () ->
|
||||
httpGet(
|
||||
"/api/hs/accounts/current", req -> req
|
||||
.header("Authorization", resolve("Bearer %{subjectName}", DROP_COMMENTS))
|
||||
)
|
||||
.expecting(OK).expecting(JSON).expectObject()
|
||||
.extractValue("subject.name", "returnedSubjectName")
|
||||
.extractValue("person.givenName", "returnedGivenName")
|
||||
.extractValue("globalAdmin", "returnedGlobalAdmin")
|
||||
).expecting(OK).expecting(JSON);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void verify(final UseCase<CurrentLoginUser>.HttpResponse response) {
|
||||
|
||||
assertThat(resolve("%{returnedSubjectName}", DROP_COMMENTS))
|
||||
.isEqualTo(resolve("%{subjectName}", DROP_COMMENTS));
|
||||
|
||||
assertThat(resolve("%{returnedGivenName}", DROP_COMMENTS))
|
||||
.isEqualTo(resolve("%{personGivenName}", DROP_COMMENTS));
|
||||
|
||||
assertThat(resolve("%{returnedGlobalAdmin}", DROP_COMMENTS))
|
||||
.isEqualTo(resolve("%{expectedToBeGlobalAdmin}", DROP_COMMENTS));
|
||||
|
||||
super.verify(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.hostsharing.hsadminng.hs.accounts.scenarios;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.scenarios.UseCase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.resolve;
|
||||
import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.resolveJsonArray;
|
||||
import static net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class FetchRbacContext extends UseCase<FetchRbacContext> {
|
||||
|
||||
public FetchRbacContext(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
introduction("Fetches the RBAC context for the login user / current subject.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
return obtain(
|
||||
"RBAC Context", () ->
|
||||
httpGet(
|
||||
"/api/rbac/context", req -> req
|
||||
.header("Authorization", resolve("Bearer %{subjectName}", DROP_COMMENTS))
|
||||
.header("assumed-roles", resolve("%{assumedRoles}", DROP_COMMENTS))
|
||||
)
|
||||
.expecting(OK).expecting(JSON).expectObject()
|
||||
.extractValue("subject.name", "returnedSubjectName")
|
||||
.extractValue("assumedRoles", "returnedAssumedRoles")
|
||||
.extractValue("globalAdmin", "returnedGlobalAdmin")
|
||||
).expecting(OK).expecting(JSON);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void verify(final UseCase<FetchRbacContext>.HttpResponse response) {
|
||||
|
||||
// HOWTO: assert in UseCase.verify()
|
||||
|
||||
assertThat(resolve("%{returnedSubjectName}", DROP_COMMENTS))
|
||||
.isEqualTo(resolve("%{subjectName}", DROP_COMMENTS));
|
||||
|
||||
assertThat(resolveJsonArray("%{returnedAssumedRoles}")
|
||||
.stream().map(m -> m.get("roleName")).toList())
|
||||
.isEqualTo(List.of(resolve("%{assumedRoles}", DROP_COMMENTS).split(";")));
|
||||
|
||||
assertThat(resolve("%{returnedGlobalAdmin}", DROP_COMMENTS))
|
||||
.isEqualTo(resolve("%{expectedToBeGlobalAdmin}", DROP_COMMENTS));
|
||||
|
||||
super.verify(response);
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -78,6 +78,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
|
||||
@MockitoBean
|
||||
HttpServletRequest request;
|
||||
|
||||
@Nested
|
||||
class CreateDebitor {
|
||||
|
||||
@@ -242,9 +243,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
// then
|
||||
allTheseDebitorsAreReturned(
|
||||
result,
|
||||
"debitor(D-1000111: rel(anchor='LP First GmbH', type='DEBITOR', holder='LP First GmbH'), fir)",
|
||||
"debitor(D-1000212: rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type='DEBITOR', holder='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.'), sec)",
|
||||
"debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
|
||||
"debitor(D-1000111: rel(anchor='LP First GmbH', type=DEBITOR, holder='LP First GmbH'), fir)",
|
||||
"debitor(D-1000212: rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type=DEBITOR, holder='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.'), sec)",
|
||||
"debitor(D-1000313: rel(anchor='IF Third OHG', type=DEBITOR, holder='IF Third OHG'), thi)");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@@ -293,7 +294,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
|
||||
// then
|
||||
assertThat(result).map(Object::toString).contains(
|
||||
"debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
|
||||
"debitor(D-1000313: rel(anchor='IF Third OHG', type=DEBITOR, holder='IF Third OHG'), thi)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +311,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
|
||||
// then
|
||||
exactlyTheseDebitorsAreReturned(result,
|
||||
"debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
|
||||
"debitor(D-1000313: rel(anchor='IF Third OHG', type=DEBITOR, holder='IF Third OHG'), thi)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +327,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
final var result = debitorRepo.findDebitorsByOptionalNameLike("third contact");
|
||||
|
||||
// then
|
||||
exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)");
|
||||
exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: rel(anchor='IF Third OHG', type=DEBITOR, holder='IF Third OHG'), thi)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -413,7 +413,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
final var newPartnerPersonUuid = givenPartner.getPartnerRel().getHolder().getUuid();
|
||||
assertThat(relationRepo.findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData(newPartnerPersonUuid, EX_PARTNER, null, null, null))
|
||||
.map(HsOfficeRelation::toShortString)
|
||||
.contains("rel(anchor='NP Winkler, Paul', type='EX_PARTNER', holder='UF Erben Bessler')");
|
||||
.contains("rel(anchor='NP Winkler, Paul', type=EX_PARTNER, holder='UF Erben Bessler')");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
@@ -144,6 +144,7 @@ class HsOfficePersonEntityUnitTest {
|
||||
|
||||
assertThat(actualDisplay).isEqualTo("person(salutation='Herr', title='Prof. Dr.', familyName='some family name', givenName='some given name')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringWithSalutationAndWithoutTitleSkipsTitle() {
|
||||
final var givenPersonEntity = HsOfficePersonRbacEntity.builder()
|
||||
|
||||
+18
-1
@@ -258,6 +258,23 @@ class HsOfficePersonRbacRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findPersonsrepresentedByPersonWithUuid() {
|
||||
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var personUuid = personRbacRepo.findPersonByOptionalNameLike("Fouler").getFirst().getUuid();
|
||||
|
||||
// when
|
||||
@SuppressWarnings("unchecked") final List<HsOfficePersonRbacEntity> representedPersons = personRbacRepo.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 auditJournalLogIsAvailable() {
|
||||
// given
|
||||
@@ -265,7 +282,7 @@ class HsOfficePersonRbacRepositoryIntegrationTest 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();
|
||||
|
||||
+5
-5
@@ -62,10 +62,10 @@ class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWith
|
||||
context("superuser-alex@hostsharing.net"); // just to be able to access RBAc-entities persons+contact
|
||||
exactlyTheseRelationsAreReturned(
|
||||
result,
|
||||
"rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')",
|
||||
"rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Smith, Peter', contact='sixth contact')",
|
||||
"rel(anchor='NP Smith, Peter', type='DEBITOR', holder='NP Smith, Peter', contact='third contact')",
|
||||
"rel(anchor='IF Third OHG', type='SUBSCRIBER', mark='members-announce', holder='NP Smith, Peter', contact='third contact')"
|
||||
"rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type=REPRESENTATIVE, holder='NP Smith, Peter', contact='second contact')",
|
||||
"rel(anchor='LP Hostsharing eG', type=PARTNER, holder='NP Smith, Peter', contact='sixth contact')",
|
||||
"rel(anchor='NP Smith, Peter', type=DEBITOR, holder='NP Smith, Peter', contact='third contact')",
|
||||
"rel(anchor='IF Third OHG', type=SUBSCRIBER, mark='members-announce', holder='NP Smith, Peter', contact='third contact')"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class HsOfficeRealRelationRepositoryIntegrationTest extends ContextBasedTestWith
|
||||
context("superuser-alex@hostsharing.net"); // just to be able to access RBAc-entities persons+contact
|
||||
exactlyTheseRelationsAreReturned(
|
||||
result,
|
||||
"rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')"
|
||||
"rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type=REPRESENTATIVE, holder='NP Smith, Peter', contact='second contact')"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -126,7 +126,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
|
||||
final var stored = relationRbacRepo.findByUuid(result.returnedValue().getUuid());
|
||||
assertThat(stored).isNotEmpty().map(HsOfficeRelation::toString).get()
|
||||
.isEqualTo(
|
||||
"rel(anchor='UF Erben Bessler', type='SUBSCRIBER', mark='operations-announce', holder='NP Winkler, Paul', contact='fourth contact')");
|
||||
"rel(anchor='UF Erben Bessler', type=SUBSCRIBER, mark='operations-announce', holder='NP Winkler, Paul', contact='fourth contact')");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -213,9 +213,9 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
|
||||
// then
|
||||
allTheseRelationsAreReturned(
|
||||
result,
|
||||
"rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Smith, Peter', contact='sixth contact')",
|
||||
"rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')",
|
||||
"rel(anchor='IF Third OHG', type='SUBSCRIBER', mark='members-announce', holder='NP Smith, Peter', contact='third contact')");
|
||||
"rel(anchor='LP Hostsharing eG', type=PARTNER, holder='NP Smith, Peter', contact='sixth contact')",
|
||||
"rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type=REPRESENTATIVE, holder='NP Smith, Peter', contact='second contact')",
|
||||
"rel(anchor='IF Third OHG', type=SUBSCRIBER, mark='members-announce', holder='NP Smith, Peter', contact='third contact')");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -237,10 +237,10 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
|
||||
// then:
|
||||
exactlyTheseRelationsAreReturned(
|
||||
result,
|
||||
"rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')",
|
||||
"rel(anchor='IF Third OHG', type='SUBSCRIBER', mark='members-announce', holder='NP Smith, Peter', contact='third contact')",
|
||||
"rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Smith, Peter', contact='sixth contact')",
|
||||
"rel(anchor='NP Smith, Peter', type='DEBITOR', holder='NP Smith, Peter', contact='third contact')");
|
||||
"rel(anchor='LP Peter Smith - The Second Hand and Thrift Stores-n-Shipping e.K.', type=REPRESENTATIVE, holder='NP Smith, Peter', contact='second contact')",
|
||||
"rel(anchor='IF Third OHG', type=SUBSCRIBER, mark='members-announce', holder='NP Smith, Peter', contact='third contact')",
|
||||
"rel(anchor='LP Hostsharing eG', type=PARTNER, holder='NP Smith, Peter', contact='sixth contact')",
|
||||
"rel(anchor='NP Smith, Peter', type=DEBITOR, holder='NP Smith, Peter', contact='third contact')");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -383,7 +383,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent().get()
|
||||
.matches(mandate -> {
|
||||
assertThat(mandate.getDebitor().toString()).isEqualTo("debitor(D-1000111: rel(anchor='LP First GmbH', type='DEBITOR', holder='LP First GmbH'), fir)");
|
||||
assertThat(mandate.getDebitor().toString()).isEqualTo("debitor(D-1000111: rel(anchor='LP First GmbH', type=DEBITOR, holder='LP First GmbH'), fir)");
|
||||
assertThat(mandate.getBankAccount().toShortString()).isEqualTo("First GmbH");
|
||||
assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z - patched");
|
||||
assertThat(mandate.getValidFrom()).isEqualTo("2020-06-05");
|
||||
@@ -424,7 +424,8 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
|
||||
// finally, the sepaMandate is actually updated
|
||||
assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent().get()
|
||||
.matches(mandate -> {
|
||||
assertThat(mandate.getDebitor().toString()).isEqualTo("debitor(D-1000111: rel(anchor='LP First GmbH', type='DEBITOR', holder='LP First GmbH'), fir)");
|
||||
assertThat(mandate.getDebitor().toString())
|
||||
.isEqualTo("debitor(D-1000111: rel(anchor='LP First GmbH', type=DEBITOR, holder='LP First GmbH'), fir)");
|
||||
assertThat(mandate.getBankAccount().toShortString()).isEqualTo("First GmbH");
|
||||
assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z");
|
||||
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2023-01-01)");
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package net.hostsharing.hsadminng.hs.scenarios;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver;
|
||||
import org.apache.commons.collections4.SetUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -17,6 +19,7 @@ import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
@@ -159,7 +162,7 @@ public abstract class ScenarioTest extends ContextBasedTest {
|
||||
assertThat(knowVariables().containsKey(declaredAlias))
|
||||
.as("@Producer method " + currentTestMethod.getName() +
|
||||
" did declare but not produce \"" + declaredAlias + "\"")
|
||||
.isTrue() );
|
||||
.isTrue());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -203,6 +206,14 @@ public abstract class ScenarioTest extends ContextBasedTest {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static List<Map<String, Object>> resolveJsonArray(final String text) {
|
||||
return new ObjectMapper().readValue(
|
||||
resolve(text, DROP_COMMENTS),
|
||||
new TypeReference<>() {}
|
||||
);
|
||||
}
|
||||
|
||||
public static Object resolveTyped(final String resolvableText) {
|
||||
final var resolved = resolve(resolvableText, DROP_COMMENTS);
|
||||
try {
|
||||
|
||||
@@ -155,18 +155,26 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpGet(final String uriPathWithPlaceholders) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
|
||||
final var request = HttpRequest.newBuilder()
|
||||
public final HttpResponse httpGet(
|
||||
final String uriPathWithPlaceholder,
|
||||
final Function<HttpRequest.Builder, HttpRequest.Builder> requestCustomizer) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholder, DROP_COMMENTS);
|
||||
final var requestBuilder = HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
.header("Authorization", "Bearer " + ScenarioTest.RUN_AS_USER)
|
||||
.timeout(seconds(HTTP_TIMEOUT_SECONDS))
|
||||
.build();
|
||||
.timeout(seconds(HTTP_TIMEOUT_SECONDS));
|
||||
final var customizedRequestBuilder = requestCustomizer.apply(requestBuilder);
|
||||
final var request = customizedRequestBuilder.build();
|
||||
final var response = client.send(request, BodyHandlers.ofString());
|
||||
return new HttpResponse(HttpMethod.GET, uriPath, null, response);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpGet(final String uriPathWithPlaceholders) {
|
||||
return httpGet(uriPathWithPlaceholders,
|
||||
req -> req.header("Authorization", "Bearer " + ScenarioTest.RUN_AS_USER));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpPost(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
|
||||
|
||||
@@ -68,7 +68,7 @@ class ContextIntegrationTests {
|
||||
|
||||
assertThat(context.fetchCurrentSubjectUuid()).isNotNull();
|
||||
|
||||
assertThat(context.fetchAssumedRoles()).isEmpty();
|
||||
assertThat(context.fetchAssumedRolesNames()).isEmpty();
|
||||
|
||||
assertThat(context.fetchCurrentSubjectOrAssumedRolesUuids())
|
||||
.containsExactly(context.fetchCurrentSubjectUuid());
|
||||
@@ -90,7 +90,7 @@ class ContextIntegrationTests {
|
||||
|
||||
assertThat(context.fetchCurrentSubjectUuid()).isNotNull();
|
||||
|
||||
assertThat(context.fetchAssumedRoles()).isEqualTo(Array.of("rbactest.package#yyy00:ADMIN"));
|
||||
assertThat(context.fetchAssumedRolesNames()).isEqualTo(Array.of("rbactest.package#yyy00:ADMIN"));
|
||||
|
||||
assertThat(context.fetchCurrentSubjectOrAssumedRolesUuids())
|
||||
.containsExactly(context.fetchCurrentSubjectOrAssumedRolesUuids());
|
||||
@@ -133,7 +133,7 @@ class ContextIntegrationTests {
|
||||
assertThat(currentSubject).isEqualTo("superuser-alex@hostsharing.net");
|
||||
|
||||
// then
|
||||
assertThat(context.fetchAssumedRoles())
|
||||
assertThat(context.fetchAssumedRolesNames())
|
||||
.isEqualTo(Array.of("rbactest.customer#xxx:OWNER", "rbactest.customer#yyy:OWNER"));
|
||||
assertThat(context.fetchCurrentSubjectOrAssumedRolesUuids()).hasSize(2);
|
||||
}
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package net.hostsharing.hsadminng.rbac.context;
|
||||
|
||||
import net.hostsharing.hsadminng.config.DisableSecurityConfig;
|
||||
import net.hostsharing.hsadminng.config.MessageTranslator;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.role.RbacRoleEntity;
|
||||
import net.hostsharing.hsadminng.rbac.role.RbacRoleRepository;
|
||||
import net.hostsharing.hsadminng.rbac.role.RbacRoleType;
|
||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
|
||||
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository;
|
||||
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;
|
||||
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 jakarta.persistence.SynchronizationType;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
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(RbacContextController.class)
|
||||
@Import({ StrictMapper.class, DisableSecurityConfig.class, MessageTranslator.class })
|
||||
@ActiveProfiles("test")
|
||||
class RbacContextControllerRestTest {
|
||||
|
||||
private static final String GIVEN_SUBJECT_NAME = "superuser-alex@hostsharing.net";
|
||||
private static final boolean GIVEN_GLOBAL_ADMIN = true;
|
||||
|
||||
@Autowired
|
||||
MockMvc mockMvc;
|
||||
|
||||
@MockitoBean
|
||||
Context contextMock;
|
||||
|
||||
@MockitoBean
|
||||
RbacRoleRepository rbacRoleRepository;
|
||||
|
||||
@MockitoBean
|
||||
RbacSubjectRepository rbacSubjectRepository;
|
||||
|
||||
@MockitoBean
|
||||
EntityManagerWrapper em;
|
||||
|
||||
@MockitoBean
|
||||
EntityManagerFactory emf;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
when(emf.createEntityManager()).thenReturn(em);
|
||||
when(emf.createEntityManager(any(Map.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class))).thenReturn(em);
|
||||
when(emf.createEntityManager(any(SynchronizationType.class), any(Map.class))).thenReturn(em);
|
||||
|
||||
// current subject uuid mock
|
||||
final var mockUuid = UUID.randomUUID();
|
||||
when(contextMock.fetchCurrentSubjectUuid()).thenReturn(mockUuid);
|
||||
|
||||
// find by uuid mock
|
||||
final var mockSubject = new RbacSubjectEntity();
|
||||
mockSubject.setUuid(mockUuid);
|
||||
mockSubject.setName(GIVEN_SUBJECT_NAME);
|
||||
when(rbacSubjectRepository.findByUuid(mockUuid)).thenReturn(mockSubject);
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiContextWillReturnCurrentContext() throws Exception {
|
||||
|
||||
// given
|
||||
final var rolesToAssume = "rbactest.package#xxx00:OWNER;rbactest.package#yyy00:OWNER";
|
||||
when(contextMock.isGlobalAdmin()).thenReturn(GIVEN_GLOBAL_ADMIN);
|
||||
when(rbacRoleRepository.fetchAssumedRoles()).thenReturn(
|
||||
Arrays.stream(rolesToAssume.split(";"))
|
||||
.map(RbacRoleDescriptor::fromRoleName)
|
||||
.map(roleDesc -> new RbacRoleEntity(
|
||||
UUID.randomUUID(), UUID.randomUUID(),
|
||||
roleDesc.tableName, roleDesc.objectIdName, roleDesc.roleType,
|
||||
roleDesc.roleName))
|
||||
.toList()
|
||||
);
|
||||
|
||||
// when
|
||||
mockMvc.perform(MockMvcRequestBuilders
|
||||
.get("/api/rbac/context")
|
||||
.header("Authorization", "Bearer " + GIVEN_SUBJECT_NAME)
|
||||
.header("assumed-roles", rolesToAssume)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(print())
|
||||
|
||||
// then
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.subject.name", is(GIVEN_SUBJECT_NAME)))
|
||||
.andExpect(jsonPath("$.globalAdmin", is(GIVEN_GLOBAL_ADMIN)))
|
||||
.andExpect(jsonPath("$.assumedRoles", hasSize(2)))
|
||||
.andExpect(jsonPath("$.assumedRoles[0].roleName", is("rbactest.package#xxx00:OWNER")))
|
||||
.andExpect(jsonPath("$.assumedRoles[1].roleName", is("rbactest.package#yyy00:OWNER")));
|
||||
}
|
||||
|
||||
record RbacRoleDescriptor(String roleName, String tableName, String objectIdName, RbacRoleType roleType) {
|
||||
|
||||
private static RbacRoleDescriptor fromRoleName(final String roleName) {
|
||||
final var tablePlus = roleName.split("#");
|
||||
final var tableName = tablePlus[0];
|
||||
final var objectId = tablePlus[1].split(":")[0];
|
||||
final var roleType = RbacRoleType.valueOf(tablePlus[1].split(":")[1]);
|
||||
return new RbacRoleDescriptor(roleName, tableName, objectId, roleType);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -100,6 +100,6 @@ class RbacGrantsDiagramServiceIntegrationTest extends ContextBasedTestWithCleanu
|
||||
final var targetObject = (UUID) em.createNativeQuery("SELECT uuid FROM hs_office.coopassettx WHERE reference='ref 1000101-1'").getSingleResult();
|
||||
final var graph = grantsMermaidService.allGrantsFrom(targetObject, "view", EnumSet.of(Include.USERS));
|
||||
|
||||
RbacGrantsDiagramService.writeToFile(join(";", context.fetchAssumedRoles()), graph, "doc/all-grants.md");
|
||||
RbacGrantsDiagramService.writeToFile(join(";", context.fetchAssumedRolesNames()), graph, "doc/all-grants.md");
|
||||
}
|
||||
}
|
||||
|
||||
+25
@@ -177,6 +177,31 @@ class RbacRoleRepositoryIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FetchAssumedRoles {
|
||||
|
||||
@Test
|
||||
void someSubject_withoutAssumedRole_fetchesNoAssumedRoles() {
|
||||
context.define("customer-admin@xxx.example.com");
|
||||
|
||||
final var result = rbacRoleRepository.fetchAssumedRoles();
|
||||
|
||||
assertThat(result).isNotNull().hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void someSubject_withAssumedRoles_fetchesAssumedRoles() {
|
||||
context.define("customer-admin@xxx.example.com",
|
||||
"rbactest.package#xxx00:OWNER;rbactest.package#xxx01:OWNER;rbactest.package#xxx02:OWNER");
|
||||
|
||||
final var result = rbacRoleRepository.fetchAssumedRoles();
|
||||
|
||||
assertThat(result).isNotNull().hasSize(3)
|
||||
.extracting(RbacRoleEntity::getRoleName)
|
||||
.contains("rbactest.package#xxx00:OWNER", "rbactest.package#xxx01:OWNER", "rbactest.package#xxx02:OWNER");
|
||||
}
|
||||
}
|
||||
|
||||
void exactlyTheseRbacRolesAreReturned(final List<RbacRoleEntity> actualResult, final String... expectedRoleNames) {
|
||||
assertThat(actualResult)
|
||||
.extracting(RbacRoleEntity::getRoleName)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.hostsharing.hsadminng.rbac.test;
|
||||
|
||||
import net.hostsharing.hsadminng.persistence.ImmutableBaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.grant.RbacGrantEntity;
|
||||
@@ -52,7 +53,7 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
|
||||
@Autowired
|
||||
JpaAttempt jpaAttempt;
|
||||
|
||||
private TreeMap<UUID, Class<? extends BaseEntity>> entitiesToCleanup = new TreeMap<>();
|
||||
private TreeMap<UUID, Class<? extends ImmutableBaseEntity>> entitiesToCleanup = new TreeMap<>();
|
||||
|
||||
private static Long latestIntialTestDataSerialId;
|
||||
private static boolean countersInitialized = false;
|
||||
@@ -67,19 +68,19 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
|
||||
|
||||
private TestInfo testInfo;
|
||||
|
||||
public <T extends BaseEntity> T refresh(final T entity) {
|
||||
public <T extends ImmutableBaseEntity> T refresh(final T entity) {
|
||||
final var merged = em.merge(entity);
|
||||
em.refresh(merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
public UUID toCleanup(final Class<? extends BaseEntity> entityClass, final UUID uuidToCleanup) {
|
||||
public UUID toCleanup(final Class<? extends ImmutableBaseEntity> entityClass, final UUID uuidToCleanup) {
|
||||
out.println("toCleanup(" + entityClass.getSimpleName() + ", " + uuidToCleanup + ")");
|
||||
entitiesToCleanup.put(uuidToCleanup, entityClass);
|
||||
return uuidToCleanup;
|
||||
}
|
||||
|
||||
public <E extends BaseEntity> E toCleanup(final E entity) {
|
||||
public <E extends ImmutableBaseEntity> E toCleanup(final E entity) {
|
||||
out.println("toCleanup(" + entity.getClass() + ", " + entity.getUuid());
|
||||
if ( entity.getUuid() == null ) {
|
||||
throw new IllegalArgumentException("only persisted entities with valid uuid allowed");
|
||||
|
||||
Reference in New Issue
Block a user