1
0

introduce defineContext replacing explicit "set local current..."

This commit is contained in:
Michael Hoennig
2022-08-30 09:35:59 +02:00
parent 8045b66324
commit a1c3e95032
19 changed files with 328 additions and 248 deletions

View File

@@ -1,52 +1,104 @@
package net.hostsharing.hsadminng.context;
import net.hostsharing.test.JpaAttempt;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
@ComponentScan(basePackageClasses = Context.class)
@ComponentScan(basePackageClasses = { Context.class, JpaAttempt.class })
@DirtiesContext
class ContextIntegrationTests {
@Autowired
private Context context;
@Test
void registerWithoutHttpServletRequestUsesCallStack() {
@MockBean
private HttpServletRequest request;
context.define("current-user", null);
@Autowired
private JpaAttempt jpaAttempt;
@Test
void defineWithoutHttpServletRequestUsesCallStack() {
context.define("mike@hostsharing.net", null);
final var currentTask = context.getCurrentTask();
assertThat(currentTask).isEqualTo("ContextIntegrationTests.registerWithoutHttpServletRequestUsesCallStack");
assertThat(currentTask).isEqualTo("ContextIntegrationTests.defineWithoutHttpServletRequestUsesCallStack");
}
@Test
@Transactional
void setCurrentUser() {
void defineWithCurrentUserButWithoutAssumedRoles() {
context.define("mike@hostsharing.net");
final var currentUser = context.getCurrentUser();
assertThat(currentUser).isEqualTo("mike@hostsharing.net");
final var assumedRoles = context.getAssumedRoles();
assertThat(assumedRoles).isEmpty();
final var currentUserUuid = context.getCurrentUserUUid();
assertThat(currentUserUuid).isNotNull();
final var assumedRoleUuids = context.currentSubjectsUuids();
assertThat(assumedRoleUuids).containsExactly(currentUserUuid);
}
@Test
void defineWithoutCurrentUserButWithAssumedRoles() {
// when
final var result = jpaAttempt.transacted(() ->
context.define(null, "package#yyy00.admin")
);
// then
result.assertExceptionWithRootCauseMessage(
javax.persistence.PersistenceException.class,
"ERROR: [403] undefined has no permission to assume role package#yyy00.admin");
}
@Test
void defineWithUnknownCurrentUserButWithAssumedRoles() {
// when
final var result = jpaAttempt.transacted(() ->
context.define("unknown@example.org", "package#yyy00.admin")
);
// then
result.assertExceptionWithRootCauseMessage(
javax.persistence.PersistenceException.class,
"ERROR: [403] undefined has no permission to assume role package#yyy00.admin");
}
@Test
@Transactional
void assumeRoles() {
void defineWithCurrentUserAndAssumedRoles() {
context.define("mike@hostsharing.net", "customer#xxx.owner;customer#yyy.owner");
final var currentUser = context.getCurrentUser();
assertThat(currentUser).isEqualTo("mike@hostsharing.net");
final var assumedRoles = context.getAssumedRoles();
assertThat(assumedRoles).containsExactlyInAnyOrder("customer#xxx.owner", "customer#yyy.owner");
final var assumedRoles = context.currentSubjectsUuids();
assertThat(assumedRoles).hasSize(2);
}
@Test
public void defineContextWithCurrentUserAndAssumeInaccessibleRole() {
// when
final var result = jpaAttempt.transacted(() ->
context.define("customer-admin@xxx.example.com", "package#yyy00.admin")
);
// then
result.assertExceptionWithRootCauseMessage(
javax.persistence.PersistenceException.class,
"ERROR: [403] user customer-admin@xxx.example.com has no permission to assume role package#yyy00#admin");
}
}

View File

@@ -26,11 +26,13 @@ import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class ContextUnitTest {
@Mock
EntityManager em;
@Mock
Query nativeQuery;
private String defineContextQueryString = """
call defineContext(
cast(:currentTask as varchar),
cast(:currentRequest as varchar),
cast(:currentUser as varchar),
cast(:assumedRoles as varchar));
""";
@Nested
class WithoutHttpRequest {
@@ -56,7 +58,7 @@ class ContextUnitTest {
context.define("current-user");
verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);");
verify(em).createNativeQuery(defineContextQueryString);
verify(nativeQuery).setParameter(
"currentTask",
"WithoutHttpRequest.registerWithoutHttpServletRequestUsesCallStackForTask");
@@ -68,7 +70,7 @@ class ContextUnitTest {
context.define("current-user");
verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);");
verify(em).createNativeQuery(defineContextQueryString);
verify(nativeQuery).setParameter("currentRequest", "");
}
}
@@ -113,7 +115,7 @@ class ContextUnitTest {
context.define("current-user");
verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);");
verify(em).createNativeQuery(defineContextQueryString);
verify(nativeQuery).setParameter("currentTask", "POST http://localhost:9999/api/endpoint");
}
@@ -127,7 +129,7 @@ class ContextUnitTest {
context.define("current-user");
verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);");
verify(em).createNativeQuery(defineContextQueryString);
verify(nativeQuery).setParameter("currentRequest", """
curl -0 -v -X POST http://localhost:9999/api/endpoint \\
-H 'current-user:given-user' \\
@@ -150,7 +152,7 @@ class ContextUnitTest {
context.define("current-user");
verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);");
verify(em).createNativeQuery(defineContextQueryString);
verify(nativeQuery).setParameter(eq("currentTask"), argThat((String t) -> t.length() == 96));
}
@@ -169,7 +171,7 @@ class ContextUnitTest {
context.define("current-user");
verify(em).createNativeQuery("call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);");
verify(em).createNativeQuery(defineContextQueryString);
verify(nativeQuery).setParameter(eq("currentRequest"), argThat((String t) -> t.length() == 512));
}

View File

@@ -6,13 +6,13 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.UUID;
@@ -30,6 +30,9 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest {
@Autowired
EntityManager em;
@MockBean
HttpServletRequest request;
@Nested
class CreateCustomer {
@@ -144,50 +147,6 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest {
exactlyTheseCustomersAreReturned(result, "xxx");
}
@Test
public void customerAdmin_withAssumedAlienPackageAdminRole_cannotViewAnyCustomer() {
// given:
context("customer-admin@xxx.example.com", "package#yyy00.admin");
// when
final var result = attempt(
em,
() -> customerRepository.findCustomerByOptionalPrefixLike(null));
// then
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"[403] user customer-admin@xxx.example.com", "has no permission to assume role package#yyy00#admin");
}
@Test
void unknownUser_withoutAssumedRole_cannotViewAnyCustomers() {
context("unknown@example.org", null);
final var result = attempt(
em,
() -> customerRepository.findCustomerByOptionalPrefixLike(null));
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
}
@Test
@Transactional
void unknownUser_withAssumedCustomerRole_cannotViewAnyCustomers() {
context("unknown@example.org", "customer#xxx.admin");
final var result = attempt(
em,
() -> customerRepository.findCustomerByOptionalPrefixLike(null));
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
}
}
@Nested

View File

@@ -7,16 +7,15 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
@@ -36,6 +35,9 @@ class PackageRepositoryIntegrationTest {
@Autowired
JpaAttempt jpaAttempt;
@MockBean
HttpServletRequest request;
@Nested
class FindAllByOptionalNameLike {
@@ -83,50 +85,6 @@ class PackageRepositoryIntegrationTest {
exactlyThesePackagesAreReturned(result, "xxx00");
}
@Test
public void customerAdmin_withAssumedAlienPackageAdminRole_cannotViewAnyPackages() {
// given:
context.define("customer-admin@xxx.example.com", "package#yyy00.admin");
// when
final var result = attempt(
em,
() -> packageRepository.findAllByOptionalNameLike(null));
// then
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"[403] user customer-admin@xxx.example.com", "has no permission to assume role package#yyy00#admin");
}
@Test
void unknownUser_withoutAssumedRole_cannotViewAnyPackages() {
context.define("unknown@example.org");
final var result = attempt(
em,
() -> packageRepository.findAllByOptionalNameLike(null));
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
}
@Test
@Transactional
void unknownUser_withAssumedCustomerRole_cannotViewAnyPackages() {
context.define("unknown@example.org", "customer#xxx.admin");
final var result = attempt(
em,
() -> packageRepository.findAllByOptionalNameLike(null));
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
}
}
@Nested

View File

@@ -202,8 +202,30 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
}
@Test
@Accepts({ "GRT:R(Read)" })
void packageAdmin_withAssumedUnixUserAdmin_canNotReadItsOwnGrantById() {
@Accepts({ "GRT:R(Read)", "GRT:X(Access Control)" })
void packageAdmin_withAssumedPackageAdmin_canNotReadItsOwnGrantByIdAnymore() {
// given
final var givenCurrentUserAsPackageAdmin = new Subject(
"pac-admin-xxx00@xxx.example.com",
"package#xxx00.admin");
final var givenGranteeUser = findRbacUserByName("pac-admin-xxx00@xxx.example.com");
final var givenGrantedRole = findRbacRoleByName("package#xxx00.admin");
// when
final var grant = givenCurrentUserAsPackageAdmin.getGrantById()
.forGrantedRole(givenGrantedRole).toGranteeUser(givenGranteeUser);
// then
grant.assertThat()
.statusCode(200)
.body("grantedByRoleIdName", is("customer#xxx.admin"))
.body("grantedRoleIdName", is("package#xxx00.admin"))
.body("granteeUserName", is("pac-admin-xxx00@xxx.example.com"));
}
@Test
@Accepts({ "GRT:R(Read)", "GRT:X(Access Control)" })
void packageAdmin_withAssumedUnixUserAdmin_canNotReadItsOwnGrantByIdAnymore() {
// given
final var givenCurrentUserAsPackageAdmin = new Subject(
"pac-admin-xxx00@xxx.example.com",
@@ -217,7 +239,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
// then
grant.assertThat()
.statusCode(404);
.statusCode(401);
}
}

View File

@@ -11,6 +11,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.test.annotation.DirtiesContext;
@@ -18,6 +19,7 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.UUID;
@@ -33,6 +35,9 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
@Autowired
Context context;
@MockBean
HttpServletRequest request;
@Autowired
RbacGrantRepository rbacGrantRepository;

View File

@@ -6,11 +6,13 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.test.annotation.DirtiesContext;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import static net.hostsharing.test.JpaAttempt.attempt;
@@ -30,6 +32,9 @@ class RbacRoleRepositoryIntegrationTest {
@Autowired
EntityManager em;
@MockBean
HttpServletRequest request;
@Nested
class FindAllRbacRoles {
@@ -132,22 +137,6 @@ class RbacRoleRepositoryIntegrationTest {
"unixuser#xxx00-aaab.owner");
}
@Test
public void customerAdmin_withAssumedAlienPackageAdminRole_cannotViewAnyRbacRole() {
// given:
context.define("customer-admin@xxx.example.com", "package#yyy00.admin");
// when
final var result = attempt(
em,
() -> rbacRoleRepository.findAll());
// then
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"[403] user customer-admin@xxx.example.com", "has no permission to assume role package#yyy00#admin");
}
@Test
void unknownUser_withoutAssumedRole_cannotViewAnyRbacRoles() {
context.define("unknown@example.org");
@@ -158,20 +147,7 @@ class RbacRoleRepositoryIntegrationTest {
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
}
@Test
void unknownUser_withAssumedRbacRoleRole_cannotViewAnyRbacRoles() {
context.define("unknown@example.org", "RbacRole#xxx.admin");
final var result = attempt(
em,
() -> rbacRoleRepository.findAll());
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
"[401] currentUserUuid cannot be determined, unknown user name \"unknown@example.org\"");
}
}

View File

@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.test.annotation.DirtiesContext;
@@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.UUID;
@@ -35,6 +37,9 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
@Autowired
EntityManager em;
@MockBean
HttpServletRequest request;
@Nested
class CreateUser {

View File

@@ -11,7 +11,6 @@ import java.util.Optional;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* Wraps the 'when' part of a DataJpaTest to improve readability of tests.
@@ -55,7 +54,7 @@ public class JpaAttempt {
public <T> JpaResult<T> transacted(final Supplier<T> code) {
try {
return JpaResult.forValue(
transactionTemplate.execute(transactionStatus -> code.get()));
transactionTemplate.execute(transactionStatus -> code.get()));
} catch (final RuntimeException exc) {
return JpaResult.forException(exc);
}
@@ -114,7 +113,6 @@ public class JpaAttempt {
if (expectedExceptionClass.isAssignableFrom(exception.getClass())) {
return (E) exception;
}
fail("");
throw new AssertionError("expected " + expectedExceptionClass + " but got " + exception);
}
@@ -123,8 +121,8 @@ public class JpaAttempt {
}
public void assertExceptionWithRootCauseMessage(
final Class<? extends RuntimeException> expectedExceptionClass,
final String... expectedRootCauseMessages) {
final Class<? extends RuntimeException> expectedExceptionClass,
final String... expectedRootCauseMessages) {
assertThat(wasSuccessful()).isFalse();
final String firstRootCauseMessageLine = firstRootCauseMessageLineOf(caughtException(expectedExceptionClass));
for (String expectedRootCauseMessage : expectedRootCauseMessages) {
@@ -133,16 +131,17 @@ public class JpaAttempt {
}
public JpaResult<T> assumeSuccessful() {
assertThat(exception).isNull();;
assertThat(exception).isNull();
;
return this;
}
private String firstRootCauseMessageLineOf(final RuntimeException exception) {
final var rootCause = NestedExceptionUtils.getRootCause(exception);
return Optional.ofNullable(rootCause)
.map(Throwable::getMessage)
.map(message -> message.split("\\r|\\n|\\r\\n", 0)[0])
.orElse(null);
.map(Throwable::getMessage)
.map(message -> message.split("\\r|\\n|\\r\\n", 0)[0])
.orElse(null);
}
}