implements user granting roles to other users
This commit is contained in:
@ -4,6 +4,7 @@ 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.context.annotation.ComponentScan;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
@ -11,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = Context.class)
|
||||
@DirtiesContext
|
||||
class ContextIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
|
@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceException;
|
||||
@ -19,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
|
||||
@DirtiesContext
|
||||
class CustomerRepositoryIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
|
@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.transaction.Transactional;
|
||||
@ -18,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
|
||||
@DirtiesContext
|
||||
class PackageRepositoryIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
|
@ -15,19 +15,22 @@ 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.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { HsadminNgApplication.class, JpaAttempt.class }
|
||||
)
|
||||
@Accepts({ "ROL:S(Schema)" })
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
class RbacGrantControllerAcceptanceTest {
|
||||
|
||||
@LocalServerPort
|
||||
@ -57,23 +60,25 @@ class RbacGrantControllerAcceptanceTest {
|
||||
|
||||
// given
|
||||
final var givenNewUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
|
||||
final String givenPackageAdmin = "aaa00@aaa.example.com";
|
||||
final String givenCurrentUserPackageAdmin = "aaa00@aaa.example.com";
|
||||
final String givenAssumedRole = "package#aaa00.admin";
|
||||
final var givenOwnPackageAdminRole = "package#aaa00.admin";
|
||||
|
||||
// when
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("current-user", givenPackageAdmin)
|
||||
.header("current-user", givenCurrentUserPackageAdmin)
|
||||
.header("assumed-roles", givenAssumedRole)
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"userUuid": "%s",
|
||||
"roleUuid": "%s",
|
||||
"assumed": true,
|
||||
"empowered": false
|
||||
"grantedRoleUuid": "%s",
|
||||
"granteeUserUuid": "%s"
|
||||
}
|
||||
""".formatted(
|
||||
createRBacUser(givenNewUserName).getUuid().toString(),
|
||||
findRbacRoleByName(givenOwnPackageAdminRole).getUuid().toString())
|
||||
findRbacRoleByName(givenOwnPackageAdminRole).getUuid().toString(),
|
||||
createRBacUser(givenNewUserName).getUuid().toString())
|
||||
)
|
||||
.port(port)
|
||||
.when()
|
||||
@ -83,9 +88,11 @@ class RbacGrantControllerAcceptanceTest {
|
||||
// @formatter:on
|
||||
|
||||
// then
|
||||
assertThat(findAllGrantsOfUser(givenPackageAdmin))
|
||||
assertThat(findAllGrantsOfUser(givenCurrentUserPackageAdmin))
|
||||
.extracting(RbacGrantEntity::toDisplay)
|
||||
.contains("grant( " + givenNewUserName + " -> " + givenOwnPackageAdminRole + ": assumed )");
|
||||
.contains("{ grant assumed role " + givenOwnPackageAdminRole +
|
||||
" to user " + givenNewUserName +
|
||||
" by role " + givenAssumedRole + " }");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -94,35 +101,38 @@ class RbacGrantControllerAcceptanceTest {
|
||||
|
||||
// given
|
||||
final var givenNewUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
|
||||
final String givenPackageAdmin = "aaa00@aaa.example.com";
|
||||
final String givenCurrentUserPackageAdmin = "aaa00@aaa.example.com";
|
||||
final String givenAssumedRole = "package#aaa00.admin";
|
||||
final var givenAlienPackageAdminRole = "package#aab00.admin";
|
||||
|
||||
// when
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("current-user", givenPackageAdmin)
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"userUuid": "%s",
|
||||
"roleUuid": "%s",
|
||||
"assumed": true,
|
||||
"empowered": false
|
||||
}
|
||||
""".formatted(
|
||||
createRBacUser(givenNewUserName).getUuid().toString(),
|
||||
findRbacRoleByName(givenAlienPackageAdminRole).getUuid().toString())
|
||||
)
|
||||
.port(port)
|
||||
.header("current-user", givenCurrentUserPackageAdmin)
|
||||
.header("assumed-roles", givenAssumedRole)
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"assumed": true,
|
||||
"grantedRoleUuid": "%s",
|
||||
"granteeUserUuid": "%s"
|
||||
}
|
||||
""".formatted(
|
||||
findRbacRoleByName(givenAlienPackageAdminRole).getUuid().toString(),
|
||||
createRBacUser(givenNewUserName).getUuid().toString())
|
||||
)
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/rbac-grants")
|
||||
.post("http://localhost/api/rbac-grants")
|
||||
.then().assertThat()
|
||||
.statusCode(403);
|
||||
.body("message", containsString("Access to granted role"))
|
||||
.body("message", containsString("forbidden for {package#aaa00.admin}"))
|
||||
.statusCode(403);
|
||||
// @formatter:on
|
||||
|
||||
// then
|
||||
assertThat(findAllGrantsOfUser(givenPackageAdmin))
|
||||
.extracting(RbacGrantEntity::getUserName)
|
||||
assertThat(findAllGrantsOfUser(givenCurrentUserPackageAdmin))
|
||||
.extracting(RbacGrantEntity::getGranteeUserName)
|
||||
.doesNotContain(givenNewUserName);
|
||||
}
|
||||
|
||||
@ -134,9 +144,9 @@ class RbacGrantControllerAcceptanceTest {
|
||||
}
|
||||
|
||||
RbacUserEntity createRBacUser(final String userName) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
return rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), userName));
|
||||
}).returnedValue();
|
||||
return jpaAttempt.transacted(() ->
|
||||
rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), userName))
|
||||
).returnedValue();
|
||||
}
|
||||
|
||||
RbacRoleEntity findRbacRoleByName(final String roleName) {
|
||||
@ -145,5 +155,4 @@ class RbacGrantControllerAcceptanceTest {
|
||||
return rbacRoleRepository.findByRoleName(roleName);
|
||||
}).returnedValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package net.hostsharing.hsadminng.rbac.rbacgrant;
|
||||
import net.hostsharing.hsadminng.Accepts;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository;
|
||||
import net.hostsharing.hsadminng.rbac.rbacuser.RbacUserEntity;
|
||||
import net.hostsharing.hsadminng.rbac.rbacuser.RbacUserRepository;
|
||||
import net.hostsharing.test.JpaAttempt;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -11,15 +13,18 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { Context.class, RbacGrantRepository.class })
|
||||
@ComponentScan(basePackageClasses = { RbacGrantRepository.class, Context.class, JpaAttempt.class })
|
||||
@DirtiesContext
|
||||
@Accepts({ "GRT:S(Schema)" })
|
||||
class RbacGrantRepositoryIntegrationTest {
|
||||
@ -39,6 +44,9 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
@Autowired
|
||||
EntityManager em;
|
||||
|
||||
@Autowired
|
||||
JpaAttempt jpaAttempt;
|
||||
|
||||
@Nested
|
||||
class FindAllRbacGrants {
|
||||
|
||||
@ -54,7 +62,7 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
// then
|
||||
exactlyTheseRbacGrantsAreReturned(
|
||||
result,
|
||||
"grant( aaa00@aaa.example.com -> package#aaa00.admin: managed assumed empowered )");
|
||||
"{ grant assumed role package#aaa00.admin to user aaa00@aaa.example.com by role customer#aaa.admin }");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -69,28 +77,26 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
// then
|
||||
exactlyTheseRbacGrantsAreReturned(
|
||||
result,
|
||||
"grant( admin@aaa.example.com -> customer#aaa.admin: managed assumed empowered )",
|
||||
"grant( aaa00@aaa.example.com -> package#aaa00.admin: managed assumed empowered )",
|
||||
"grant( aaa01@aaa.example.com -> package#aaa01.admin: managed assumed empowered )",
|
||||
"grant( aaa02@aaa.example.com -> package#aaa02.admin: managed assumed empowered )");
|
||||
"{ grant assumed role customer#aaa.admin to user admin@aaa.example.com by role global#hostsharing.admin }",
|
||||
"{ grant assumed role package#aaa00.admin to user aaa00@aaa.example.com by role customer#aaa.admin }",
|
||||
"{ grant assumed role package#aaa01.admin to user aaa01@aaa.example.com by role customer#aaa.admin }",
|
||||
"{ grant assumed role package#aaa02.admin to user aaa02@aaa.example.com by role customer#aaa.admin }");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Accepts({ "GRT:L(List)" })
|
||||
public void customerAdmin_withAssumedRole_cannotViewRbacGrants() {
|
||||
public void customerAdmin_withAssumedRole_canOnlyViewRbacGrantsVisibleByAssumedRole() {
|
||||
// given:
|
||||
currentUser("admin@aaa.example.com");
|
||||
assumedRoles("package#aab00.admin");
|
||||
assumedRoles("package#aaa00.admin");
|
||||
|
||||
// when
|
||||
final var result = attempt(
|
||||
em,
|
||||
() -> rbacGrantRepository.findAll());
|
||||
final var result = rbacGrantRepository.findAll();
|
||||
|
||||
// then
|
||||
result.assertExceptionWithRootCauseMessage(
|
||||
JpaSystemException.class,
|
||||
"[403] user admin@aaa.example.com", "has no permission to assume role package#aab00#admin");
|
||||
exactlyTheseRbacGrantsAreReturned(
|
||||
result,
|
||||
"{ grant assumed role package#aaa00.admin to user aaa00@aaa.example.com by role customer#aaa.admin }");
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,24 +108,72 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
public void customerAdmin_canGrantOwnPackageAdminRole_toArbitraryUser() {
|
||||
// given
|
||||
currentUser("admin@aaa.example.com");
|
||||
final var userUuid = rbacUserRepository.findUuidByName("aac00@aac.example.com");
|
||||
final var roleUuid = rbacRoleRepository.findByRoleName("package#aaa00.admin").getUuid();
|
||||
assumedRoles("customer#aaa.admin");
|
||||
final var givenArbitraryUserUuid = rbacUserRepository.findUuidByName("aac00@aac.example.com");
|
||||
final var givenOwnPackageRoleUuid = rbacRoleRepository.findByRoleName("package#aaa00.admin").getUuid();
|
||||
|
||||
// when
|
||||
final var grant = RbacGrantEntity.builder()
|
||||
.userUuid(userUuid).roleUuid(roleUuid)
|
||||
.assumed(true).empowered(false)
|
||||
.granteeUserUuid(givenArbitraryUserUuid).grantedRoleUuid(givenOwnPackageRoleUuid)
|
||||
.assumed(true)
|
||||
.build();
|
||||
final var attempt = attempt(em, () ->
|
||||
rbacGrantRepository.save(grant)
|
||||
);
|
||||
|
||||
// then
|
||||
assertThat(attempt.wasSuccessful()).isTrue();
|
||||
assertThat(attempt.caughtException()).isNull();
|
||||
assertThat(rbacGrantRepository.findAll())
|
||||
.extracting(RbacGrantEntity::toDisplay)
|
||||
.contains("grant( aac00@aac.example.com -> package#aaa00.admin: assumed )");
|
||||
.contains("{ grant assumed role package#aaa00.admin to user aac00@aac.example.com by role customer#aaa.admin }");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Accepts({ "GRT:C(Create)" })
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
public void packageAdmin_canNotGrantPackageOwnerRole() {
|
||||
// given
|
||||
record Given(RbacUserEntity arbitraryUser, UUID packageOwnerRoleUuid) {}
|
||||
final var given = jpaAttempt.transacted(() -> {
|
||||
// to find the uuids of we need to have access rights to these
|
||||
currentUser("admin@aaa.example.com");
|
||||
return new Given(
|
||||
createNewUser(), // eigene Transaktion?
|
||||
rbacRoleRepository.findByRoleName("package#aaa00.owner").getUuid()
|
||||
);
|
||||
}).returnedValue();
|
||||
|
||||
// when
|
||||
final var attempt = jpaAttempt.transacted(() -> {
|
||||
// now we try to use these uuids as a less privileged user
|
||||
currentUser("aaa00@aaa.example.com");
|
||||
assumedRoles("package#aaa00.admin");
|
||||
final var grant = RbacGrantEntity.builder()
|
||||
.granteeUserUuid(given.arbitraryUser.getUuid())
|
||||
.grantedRoleUuid(given.packageOwnerRoleUuid)
|
||||
.assumed(true)
|
||||
.build();
|
||||
rbacGrantRepository.save(grant);
|
||||
});
|
||||
|
||||
// then
|
||||
attempt.assertExceptionWithRootCauseMessage(
|
||||
JpaSystemException.class,
|
||||
"ERROR: [403] Access to granted role " + given.packageOwnerRoleUuid
|
||||
+ " forbidden for {package#aaa00.admin}");
|
||||
jpaAttempt.transacted(() -> {
|
||||
currentUser(given.arbitraryUser.getName());
|
||||
assertThat(rbacGrantRepository.findAll())
|
||||
.extracting(RbacGrantEntity::toDisplay)
|
||||
.hasSize(0);
|
||||
// "{ grant assumed role package#aaa00.admin to user aac00@aac.example.com by role customer#aaa.admin }");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private RbacUserEntity createNewUser() {
|
||||
return rbacUserRepository.create(
|
||||
new RbacUserEntity(null, "test-user-" + System.currentTimeMillis() + "@example.com"));
|
||||
}
|
||||
|
||||
void currentUser(final String currentUser) {
|
||||
@ -134,7 +188,7 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
|
||||
void exactlyTheseRbacGrantsAreReturned(final List<RbacGrantEntity> actualResult, final String... expectedGrant) {
|
||||
assertThat(actualResult)
|
||||
.filteredOn(g -> !g.getUserName().startsWith("test-user-")) // ignore test-users created by other tests
|
||||
.filteredOn(g -> !g.getGranteeUserName().startsWith("test-user-")) // ignore test-users created by other tests
|
||||
.extracting(RbacGrantEntity::toDisplay)
|
||||
.containsExactlyInAnyOrder(expectedGrant);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.test.annotation.Commit;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@ -22,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { RbacUserRepository.class, Context.class, JpaAttempt.class })
|
||||
@DirtiesContext
|
||||
class RbacUserRepositoryIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
@ -58,7 +60,7 @@ class RbacUserRepositoryIntegrationTest {
|
||||
|
||||
@Test
|
||||
@Commit
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
void anyoneCanCreateTheirOwnUser_committed() {
|
||||
|
||||
// given:
|
||||
|
@ -4,8 +4,7 @@ import junit.framework.AssertionFailedError;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.NestedExceptionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.Optional;
|
||||
@ -32,20 +31,16 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class JpaAttempt {
|
||||
|
||||
@Autowired
|
||||
private final EntityManager em;
|
||||
|
||||
public JpaAttempt(final EntityManager em) {
|
||||
this.em = em;
|
||||
}
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
public static <T> JpaResult<T> attempt(final EntityManager em, final Supplier<T> code) {
|
||||
try {
|
||||
final var result = new JpaResult<T>(code.get(), null);
|
||||
final var result = JpaResult.forValue(code.get());
|
||||
em.flush();
|
||||
em.clear();
|
||||
return result;
|
||||
} catch (RuntimeException exc) {
|
||||
return new JpaResult<T>(null, exc);
|
||||
} catch (final RuntimeException exc) {
|
||||
return JpaResult.forException(exc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,29 +51,50 @@ public class JpaAttempt {
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public <T> JpaResult<T> transacted(final Supplier<T> code) {
|
||||
return attempt(em, code);
|
||||
try {
|
||||
return JpaResult.forValue(
|
||||
transactionTemplate.execute(transactionStatus -> code.get()));
|
||||
} catch (final RuntimeException exc) {
|
||||
return JpaResult.forException(exc);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void transacted(final Runnable code) {
|
||||
attempt(em, () -> {
|
||||
code.run();
|
||||
return null;
|
||||
});
|
||||
public JpaResult<Void> transacted(final Runnable code) {
|
||||
try {
|
||||
transactionTemplate.execute(transactionStatus -> {
|
||||
code.run();
|
||||
return null;
|
||||
});
|
||||
return JpaResult.forVoidValue();
|
||||
} catch (final RuntimeException exc) {
|
||||
|
||||
return new JpaResult<>(null, exc);
|
||||
}
|
||||
}
|
||||
|
||||
public static class JpaResult<T> {
|
||||
|
||||
final T result;
|
||||
final RuntimeException exception;
|
||||
private final T result;
|
||||
private final RuntimeException exception;
|
||||
|
||||
public JpaResult(final T result, final RuntimeException exception) {
|
||||
private JpaResult(final T result, final RuntimeException exception) {
|
||||
this.result = result;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
static JpaResult<Void> forVoidValue() {
|
||||
return new JpaResult<>(null, null);
|
||||
}
|
||||
|
||||
public static <T> JpaResult<T> forValue(final T value) {
|
||||
return new JpaResult<>(value, null);
|
||||
}
|
||||
|
||||
public static <T> JpaResult<T> forException(final RuntimeException exception) {
|
||||
return new JpaResult<>(null, exception);
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return exception == null;
|
||||
}
|
||||
|
Reference in New Issue
Block a user