improve test code coverage

This commit is contained in:
Michael Hoennig 2022-08-25 17:27:17 +02:00
parent 2531b9071f
commit 1a18ba4a3d
16 changed files with 485 additions and 107 deletions

View File

@ -156,11 +156,8 @@ jacocoTestReport {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
"net/hostsharing/hsadminng/generated/**/*.class",
// TODO: improve test code coverage for these classes:
"net/hostsharing/hsadminng/rbac/rbacuser/UserController.class",
"net/hostsharing/hsadminng/rbac/rbacgrant/GrantController.class",
"net/hostsharing/hsadminng/hs/hscustomer/CustomerController.class"
"net/hostsharing/hsadminng/TestController.class",
"net/hostsharing/hsadminng/hs/hscustomer/HsadminNgApplication.class"
])
}))
}
@ -174,7 +171,7 @@ jacocoTestCoverageVerification {
rule {
excludes = ['net.hostsharing.hsadminng.generated.**']
limit {
minimum = 0.7 // TODO: increase to 0.9
minimum = 0.8 // TODO: increase to 0.9
}
}
@ -190,14 +187,13 @@ jacocoTestCoverageVerification {
'net.hostsharing.hsadminng.TestController',
// TODO: improve test code coverage:
'net.hostsharing.hsadminng.rbac.rbacuser.UserController',
'net.hostsharing.hsadminng.hs.hscustomer.CustomerController'
'net.hostsharing.hsadminng.Mapper',
]
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.7
minimum = 0.95
}
}
rule {
@ -205,13 +201,7 @@ jacocoTestCoverageVerification {
excludes = [
'net.hostsharing.hsadminng.generated.**',
'net.hostsharing.hsadminng.HsadminNgApplication.*',
// TODO: improve test code coverage:
'net.hostsharing.hsadminng.rbac.rbacuser.RbacUserController.listUsers(*)',
'net.hostsharing.hsadminng.rbac.rbacuser.RbacUserController.listUserPermissions(*)',
'net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantController.listUserGrants(*)',
'net.hostsharing.hsadminng.hs.hscustomer.CustomerController.addCustomer(java.lang.String, java.lang.String, net.hostsharing.hsadminng.generated.api.v1.model.CustomerResource)'
]
'net.hostsharing.hsadminng.TestController.*']
limit {
counter = 'BRANCH'

View File

@ -1,3 +1,11 @@
# Spring BOM overrides
postgresql.version = 42.4.1
# TODO: can be removed if all dependencies are JDK 16 compliant
#org.gradle.jvmargs= \
# --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
# --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
# --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
# --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
# --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.hscustomer;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.CustomersApi;
import net.hostsharing.hsadminng.generated.api.v1.model.CustomerResource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@ -33,7 +34,7 @@ public class CustomerController implements CustomersApi {
String prefix
) {
context.setCurrentUser(userName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
@ -51,7 +52,7 @@ public class CustomerController implements CustomersApi {
context.setCurrentTask("create new customer: #" + customer.getReference() + " / " + customer.getPrefix());
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
if (customer.getUuid() == null) {
@ -62,7 +63,7 @@ public class CustomerController implements CustomersApi {
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/rbac-users/{id}")
.path("/api/customers/{id}")
.buildAndExpand(customer.getUuid())
.toUri();
return ResponseEntity.created(uri).body(map(saved, CustomerResource.class));

View File

@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.PackagesApi;
import net.hostsharing.hsadminng.generated.api.v1.model.PackageResource;
import net.hostsharing.hsadminng.generated.api.v1.model.PackageUpdateResource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@ -33,7 +34,7 @@ public class PackageController implements PackagesApi {
String name
) {
context.setCurrentUser(userName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
final var result = packageRepository.findAllByOptionalNameLike(name);
@ -49,7 +50,7 @@ public class PackageController implements PackagesApi {
final PackageUpdateResource body) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
final var current = packageRepository.findByUuid(packageUuid);

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.rbac.rbacgrant;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.RbacgrantsApi;
import net.hostsharing.hsadminng.generated.api.v1.model.RbacGrantResource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@ -38,7 +39,7 @@ public class RbacGrantController implements RbacgrantsApi {
final UUID granteeUserUuid) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
@ -57,7 +58,7 @@ public class RbacGrantController implements RbacgrantsApi {
final String assumedRoles) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
return ResponseEntity.ok(mapList(rbacGrantRepository.findAll(), RbacGrantResource.class));
@ -72,7 +73,7 @@ public class RbacGrantController implements RbacgrantsApi {
context.setCurrentTask("granting role to user");
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
@ -98,7 +99,7 @@ public class RbacGrantController implements RbacgrantsApi {
context.setCurrentTask("revoking role from user");
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.rbac.rbacrole;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.RbacrolesApi;
import net.hostsharing.hsadminng.generated.api.v1.model.RbacRoleResource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@ -29,7 +30,7 @@ public class RbacRoleController implements RbacrolesApi {
final String assumedRoles) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
return ResponseEntity.ok(mapList(rbacRoleRepository.findAll(), RbacRoleResource.class));

View File

@ -2,8 +2,11 @@ package net.hostsharing.hsadminng.rbac.rbacuser;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.RbacusersApi;
import net.hostsharing.hsadminng.generated.api.v1.model.RbacGrantResource;
import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserPermissionResource;
import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserResource;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantId;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@ -28,7 +31,7 @@ public class RbacUserController implements RbacusersApi {
@Override
@Transactional
public ResponseEntity<RbacUserResource> createUser(
@RequestBody final RbacUserResource body
final RbacUserResource body
) {
context.setCurrentTask("creating new user: " + body.getName());
context.setCurrentUser(body.getName());
@ -47,22 +50,33 @@ public class RbacUserController implements RbacusersApi {
}
@Override
public ResponseEntity<List<RbacUserPermissionResource>> getUserById(
@Transactional(readOnly = true)
public ResponseEntity<RbacUserResource> getUserById(
final String currentUser,
final String assumedRoles,
final String userName) {
return null; // TODO implement getUserById
final UUID userUuid) {
context.setCurrentUser(currentUser);
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
final var result = rbacUserRepository.findByUuid(userUuid);
if (result == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(map(result, RbacUserResource.class));
}
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<RbacUserResource>> listUsers(
@RequestHeader(name = "current-user") final String currentUserName,
@RequestHeader(name = "assumed-roles", required = false) final String assumedRoles,
@RequestParam(name = "name", required = false) final String userName
final String currentUserName,
final String assumedRoles,
final String userName
) {
context.setCurrentUser(currentUserName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
return ResponseEntity.ok(mapList(rbacUserRepository.findByOptionalNameLike(userName), RbacUserResource.class));
@ -71,14 +85,14 @@ public class RbacUserController implements RbacusersApi {
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<RbacUserPermissionResource>> listUserPermissions(
@RequestHeader(name = "current-user") final String currentUserName,
@RequestHeader(name = "assumed-roles", required = false) final String assumedRoles,
@PathVariable(name = "userName") final String userName
final String currentUserName,
final String assumedRoles,
final UUID userUuid
) {
context.setCurrentUser(currentUserName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
if (!StringUtils.isBlank(assumedRoles)) {
context.assumeRoles(assumedRoles);
}
return ResponseEntity.ok(mapList(rbacUserRepository.findPermissionsOfUser(userName), RbacUserPermissionResource.class));
return ResponseEntity.ok(mapList(rbacUserRepository.findPermissionsOfUserByUuid(userUuid), RbacUserPermissionResource.class));
}
}

View File

@ -22,8 +22,8 @@ public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
RbacUserEntity findByUuid(UUID uuid);
@Query(value = "select * from grantedPermissions(:userName)", nativeQuery = true)
List<RbacUserPermission> findPermissionsOfUser(String userName);
@Query(value = "select * from grantedPermissions(:userUuid)", nativeQuery = true)
List<RbacUserPermission> findPermissionsOfUserByUuid(UUID userUuid);
/*
Can't use save/saveAndFlush from SpringData because the uuid is not generated on the entity level,

View File

@ -13,12 +13,12 @@ paths:
/api/rbac-users:
$ref: "./api-definition/rbac-users.yaml"
/api/rbac-users/{userUuid}:
$ref: "./api-definition/rbac-users-with-id.yaml"
/api/rbac-users/{userUuid}/permissions:
$ref: "./api-definition/rbac-users-with-id-permissions.yaml"
/api/rbac-users/{userUuid}:
$ref: "./api-definition/rbac-users-with-id.yaml"
/api/rbac-roles:
$ref: "./api-definition/rbac-roles.yaml"

View File

@ -6,11 +6,12 @@ get:
parameters:
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
- name: userName
- name: userUuid
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK

View File

@ -6,20 +6,19 @@ get:
parameters:
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
- name: userName
- name: userUuid
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
content:
'application/json':
schema:
type: array
items:
$ref: './api-definition/rbac-user-schemas.yaml#/components/schemas/RbacUserPermission'
$ref: './api-definition/rbac-user-schemas.yaml#/components/schemas/RbacUser'
"401":
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'

View File

@ -205,24 +205,18 @@ grant all privileges on RbacOwnGrantedPermissions_rv to restricted;
*/
create or replace function grantedPermissions(userName varchar)
create or replace function grantedPermissions(targetUserUuid uuid)
returns table(roleUuid uuid, roleName text, permissionUuid uuid, op RbacOp, objectTable varchar, objectIdName varchar, objectUuid uuid)
returns null on null input
language plpgsql as $$
declare
targetUserId uuid;
currentUserId uuid;
begin
-- @formatter:off
if cardinality(assumedRoles()) > 0 then
raise exception '[400] grantedPermissions(...) does not support assumed roles';
end if;
targetUserId := findRbacUserId(userName);
currentUserId := currentUserId();
if hasGlobalRoleGranted(targetUserId) and not hasGlobalRoleGranted(currentUserId) then
raise exception '[403] permissions of user "%" are not accessible to user "%"', userName, currentUser();
if hasGlobalRoleGranted(targetUserUuid) and not hasGlobalRoleGranted(currentUserId) then
raise exception '[403] permissions of user "%" are not accessible to user "%"', targetUserUuid, currentUser();
end if;
return query select
@ -235,12 +229,12 @@ begin
p.uuid as permissionUuid, p.op, po.objecttable as permissionObjectTable,
findIdNameByObjectUuid(po.objectTable, po.uuid) as permissionObjectIdName,
po.uuid as permissionObjectUuid
from queryPermissionsGrantedToSubjectId( targetUserId) as p
from queryPermissionsGrantedToSubjectId( targetUserUuid) as p
join rbacgrants as g on g.descendantUuid = p.uuid
join rbacobject as po on po.uuid = p.objectUuid
join rbacrole_rv as r on r.uuid = g.ascendantUuid
join rbacobject as ro on ro.uuid = r.objectUuid
where isGranted(targetUserId, r.uuid)
where isGranted(targetUserUuid, r.uuid)
) xp;
-- @formatter:on
end; $$;

View File

@ -141,6 +141,36 @@ class CustomerControllerAcceptanceTest {
.hasValueSatisfying(c -> assertThat(c.getPrefix()).isEqualTo("vvv"));
}
@Test
void hostsharingAdmin_withAssumedCustomerAdminRole_canNotCreateCustomer() throws Exception {
RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.header("assumed-roles", "customer#xxx.admin")
.contentType(ContentType.JSON)
.body("""
{
"reference": 90010,
"prefix": "uuu",
"adminUserName": "customer-admin@uuu.example.com"
}
""")
.port(port)
.when()
.post("http://localhost/api/customers")
.then().assertThat()
.statusCode(403)
.contentType(ContentType.JSON)
.statusCode(403)
.body("message", containsString("add-customer not permitted for customer#xxx.admin"));
// @formatter:on
// finally, the new customer was not created
context.setCurrentUser("sven@hostsharing.net");
assertThat(customerRepository.findCustomerByOptionalPrefixLike("uuu")).hasSize(0);
}
@Test
void customerAdmin_withoutAssumedRole_canNotCreateCustomer() throws Exception {

View File

@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.*;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
@ -55,6 +56,108 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
@Autowired
JpaAttempt jpaAttempt;
@Nested
class ListGrants {
@Test
@Accepts("GRT:L(List)")
void hostsharingAdmin_withoutAssumedRole_canViewAllGrants() {
RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/rbac-grants")
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", hasItem(
allOf(
hasEntry("grantedByRoleIdName", "global#hostsharing.admin"),
hasEntry("grantedRoleIdName", "customer#xxx.admin"),
hasEntry("granteeUserName", "customer-admin@xxx.example.com")
)
))
.body("", hasItem(
allOf(
hasEntry("grantedByRoleIdName", "global#hostsharing.admin"),
hasEntry("grantedRoleIdName", "customer#yyy.admin"),
hasEntry("granteeUserName", "customer-admin@yyy.example.com")
)
))
.body("", hasItem(
allOf(
hasEntry("grantedByRoleIdName", "global#hostsharing.admin"),
hasEntry("grantedRoleIdName", "global#hostsharing.admin"),
hasEntry("granteeUserName", "sven@hostsharing.net")
)
))
.body("", hasItem(
allOf(
hasEntry("grantedByRoleIdName", "customer#xxx.admin"),
hasEntry("grantedRoleIdName", "package#xxx00.admin"),
hasEntry("granteeUserName", "pac-admin-xxx00@xxx.example.com")
)
))
.body("", hasItem(
allOf(
hasEntry("grantedByRoleIdName", "customer#zzz.admin"),
hasEntry("grantedRoleIdName", "package#zzz02.admin"),
hasEntry("granteeUserName", "pac-admin-zzz02@zzz.example.com")
)
))
.body("size()", greaterThanOrEqualTo(14));
// @formatter:on
}
@Test
@Accepts({ "GRT:L(List)", "GRT:X(Access Control)" })
void hostsharingAdmin_withAssumedPackageAdminRole_canViewPacketRelatedGrants() {
RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.header("assumed-roles", "package#yyy00.admin")
.port(port)
.when()
.get("http://localhost/api/rbac-grants")
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", hasItem(
allOf(
hasEntry("grantedByRoleIdName", "customer#yyy.admin"),
hasEntry("grantedRoleIdName", "package#yyy00.admin"),
hasEntry("granteeUserName", "pac-admin-yyy00@yyy.example.com")
)
))
.body("size()", is(1));
// @formatter:on
}
@Test
@Accepts({ "GRT:L(List)", "GRT:X(Access Control)" })
void packageAdmin_withoutAssumedRole_canViewPacketRelatedGrants() {
RestAssured // @formatter:off
.given()
.header("current-user", "pac-admin-yyy00@yyy.example.com")
.port(port)
.when()
.get("http://localhost/api/rbac-grants")
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", hasItem(
allOf(
hasEntry("grantedByRoleIdName", "customer#yyy.admin"),
hasEntry("grantedRoleIdName", "package#yyy00.admin"),
hasEntry("granteeUserName", "pac-admin-yyy00@yyy.example.com")
)
))
.body("size()", is(1));
// @formatter:on
}
}
@Nested
class GetGrantById {

View File

@ -2,8 +2,10 @@ package net.hostsharing.hsadminng.rbac.rbacuser;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.Accepts;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.test.JpaAttempt;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@ -19,7 +21,7 @@ import static org.hamcrest.Matchers.*;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = HsadminNgApplication.class
classes = { HsadminNgApplication.class, JpaAttempt.class }
)
@Transactional
class RbacUserControllerAcceptanceTest {
@ -30,6 +32,9 @@ class RbacUserControllerAcceptanceTest {
@Autowired
EntityManager em;
@Autowired
JpaAttempt jpaAttempt;
@Autowired
Context context;
@ -37,9 +42,125 @@ class RbacUserControllerAcceptanceTest {
RbacUserRepository rbacUserRepository;
@Nested
class ApiRbacUsersGet {
class CreateRbacUser {
@Test
@Accepts({ "USR:C(Create)", "USR:X(Access Control)" })
void anybody_canCreateANewUser() {
// @formatter:off
final var location = RestAssured
.given()
.contentType(ContentType.JSON)
.body("""
{
"name": "new-user@example.com"
}
""")
.port(port)
.when()
.post("http://localhost/api/rbac-users")
.then().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("name", is("new-user@example.com"))
.header("Location", startsWith("http://localhost"))
.extract().header("Location");
// @formatter:on
// finally, the user can view its own record
final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
context.setCurrentUser("new-user@example.com");
assertThat(rbacUserRepository.findByUuid(newUserUuid))
.extracting(RbacUserEntity::getName).isEqualTo("new-user@example.com");
}
}
@Nested
class GetRbacUser {
@Test
@Accepts({ "USR:R(Read)" })
void hostsharingAdmin_withoutAssumedRole_canGetArbitraryUser() {
final var givenUser = findRbacUserByName("pac-admin-xxx00@xxx.example.com");
// @formatter:off
RestAssured
.given()
.header("current-user", "mike@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/rbac-users/" + givenUser.getUuid())
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("name", is("pac-admin-xxx00@xxx.example.com"));
// @formatter:on
}
@Test
@Accepts({ "USR:R(Read)", "USR:X(Access Control)" })
void hostsharingAdmin_withAssumedCustomerAdminRole_canGetUserWithinInItsRealm() {
final var givenUser = findRbacUserByName("pac-admin-yyy00@yyy.example.com");
// @formatter:off
RestAssured
.given()
.header("current-user", "mike@hostsharing.net")
.header("assumed-roles", "customer#yyy.admin")
.port(port)
.when()
.get("http://localhost/api/rbac-users/" + givenUser.getUuid())
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("name", is("pac-admin-yyy00@yyy.example.com"));
// @formatter:on
}
@Test
@Accepts({ "USR:R(Read)", "USR:X(Access Control)" })
void customerAdmin_withoutAssumedRole_canGetUserWithinInItsRealm() {
final var givenUser = findRbacUserByName("pac-admin-yyy00@yyy.example.com");
// @formatter:off
RestAssured
.given()
.header("current-user", "customer-admin@yyy.example.com")
.port(port)
.when()
.get("http://localhost/api/rbac-users/" + givenUser.getUuid())
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("name", is("pac-admin-yyy00@yyy.example.com"));
// @formatter:on
}
@Test
@Accepts({ "USR:R(Read)", "USR:X(Access Control)" })
void customerAdmin_withoutAssumedRole_canNotGetUserOutsideOfItsRealm() {
final var givenUser = findRbacUserByName("pac-admin-yyy00@yyy.example.com");
// @formatter:off
RestAssured
.given()
.header("current-user", "customer-admin@xxx.example.com")
.port(port)
.when()
.get("http://localhost/api/rbac-users/" + givenUser.getUuid())
.then().log().body().assertThat()
.statusCode(404);
// @formatter:on
}
}
@Nested
class ListRbacUsers {
@Test
@Accepts({ "USR:L(List)" })
void hostsharingAdmin_withoutAssumedRole_canViewAllUsers() {
// @formatter:off
@ -65,6 +186,7 @@ class RbacUserControllerAcceptanceTest {
}
@Test
@Accepts({ "USR:F(Filter)" })
void hostsharingAdmin_withoutAssumedRole_canViewAllUsersByName() {
// @formatter:off
@ -85,6 +207,30 @@ class RbacUserControllerAcceptanceTest {
}
@Test
@Accepts({ "USR:L(List)", "USR:X(Access Control)" })
void hostsharingAdmin_withAssumedCustomerAdminRole_canViewUsersInItsRealm() {
// @formatter:off
RestAssured
.given()
.header("current-user", "mike@hostsharing.net")
.header("assumed-roles", "customer#yyy.admin")
.port(port)
.when()
.get("http://localhost/api/rbac-users")
.then().assertThat()
.statusCode(200)
.contentType("application/json")
.body("[0].name", is("customer-admin@yyy.example.com"))
.body("[1].name", is("pac-admin-yyy00@yyy.example.com"))
.body("[2].name", is("pac-admin-yyy01@yyy.example.com"))
.body("[3].name", is("pac-admin-yyy02@yyy.example.com"))
.body("size()", is(4));
// @formatter:on
}
@Test
@Accepts({ "USR:L(List)", "USR:X(Access Control)" })
void customerAdmin_withoutAssumedRole_canViewUsersInItsRealm() {
// @formatter:off
@ -106,6 +252,7 @@ class RbacUserControllerAcceptanceTest {
}
@Test
@Accepts({ "USR:L(List)", "USR:X(Access Control)" })
void packetAdmin_withoutAssumedRole_canViewAllUsersOfItsPackage() {
// @formatter:off
@ -125,37 +272,135 @@ class RbacUserControllerAcceptanceTest {
}
@Nested
class ApiRbacUsersPost {
class ListRbacUserPermissions {
@Test
void anybody_canCreateANewUser() {
@Accepts({ "PRM:L(List)" })
void hostsharingAdmin_withoutAssumedRole_canViewArbitraryUsersPermissions() {
final var givenUser = findRbacUserByName("pac-admin-yyy00@yyy.example.com");
// @formatter:off
final var location = RestAssured
RestAssured
.given()
.contentType(ContentType.JSON)
.body("""
{
"name": "new-user@example.com"
}
""")
.header("current-user", "mike@hostsharing.net")
.port(port)
.when()
.post("http://localhost/api/rbac-users")
.then().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("name", is("new-user@example.com"))
.header("Location", startsWith("http://localhost"))
.extract().header("Location");
.get("http://localhost/api/rbac-users/" + givenUser.getUuid() + "/permissions")
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", hasItem(
allOf(
hasEntry("roleName", "customer#yyy.tenant"),
hasEntry("op", "view"))
))
.body("", hasItem(
allOf(
hasEntry("roleName", "package#yyy00.admin"),
hasEntry("op", "add-unixuser"))
))
.body("", hasItem(
allOf(
hasEntry("roleName", "unixuser#yyy00-aaaa.owner"),
hasEntry("op", "*"))
))
.body("size()", is(8));
// @formatter:on
}
// finally, the user can view its own record
final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
context.setCurrentUser("new-user@example.com");
assertThat(rbacUserRepository.findByUuid(newUserUuid))
.extracting(RbacUserEntity::getName).isEqualTo("new-user@example.com");
@Test
@Accepts({ "PRM:L(List)" })
void hostsharingAdmin_withAssumedCustomerAdminRole_canViewArbitraryUsersPermissions() {
final var givenUser = findRbacUserByName("pac-admin-yyy00@yyy.example.com");
// @formatter:off
RestAssured
.given()
.header("current-user", "mike@hostsharing.net")
.header("assumed-roles", "package#yyy00.admin")
.port(port)
.when()
.get("http://localhost/api/rbac-users/" + givenUser.getUuid() + "/permissions")
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", hasItem(
allOf(
hasEntry("roleName", "customer#yyy.tenant"),
hasEntry("op", "view"))
))
.body("", hasItem(
allOf(
hasEntry("roleName", "package#yyy00.admin"),
hasEntry("op", "add-unixuser"))
))
.body("", hasItem(
allOf(
hasEntry("roleName", "unixuser#yyy00-aaaa.owner"),
hasEntry("op", "*"))
))
.body("size()", is(8));
// @formatter:on
}
@Test
@Accepts({ "PRM:L(List)" })
void packageAdmin_withoutAssumedRole_canViewPermissionsOfUsersInItsRealm() {
final var givenUser = findRbacUserByName("pac-admin-yyy00@yyy.example.com");
// @formatter:off
RestAssured
.given()
.header("current-user", "pac-admin-yyy00@yyy.example.com")
.port(port)
.when()
.get("http://localhost/api/rbac-users/" + givenUser.getUuid() + "/permissions")
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", hasItem(
allOf(
hasEntry("roleName", "customer#yyy.tenant"),
hasEntry("op", "view"))
))
.body("", hasItem(
allOf(
hasEntry("roleName", "package#yyy00.admin"),
hasEntry("op", "add-unixuser"))
))
.body("", hasItem(
allOf(
hasEntry("roleName", "unixuser#yyy00-aaaa.owner"),
hasEntry("op", "*"))
))
.body("size()", is(8));
// @formatter:on
}
@Test
@Accepts({ "PRM:L(List)" })
void packageAdmin_canViewPermissionsOfUsersOutsideOfItsRealm() {
final var givenUser = findRbacUserByName("pac-admin-xxx00@xxx.example.com");
// @formatter:off
RestAssured
.given()
.header("current-user", "pac-admin-yyy00@yyy.example.com")
.port(port)
.when()
.get("http://localhost/api/rbac-users/" + givenUser.getUuid() + "/permissions")
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("size()", is(0));
// @formatter:on
}
}
RbacUserEntity findRbacUserByName(final String userName) {
return jpaAttempt.transacted(() -> {
context.setCurrentUser("mike@hostsharing.net");
return rbacUserRepository.findByName(userName);
}).returnedValue();
}
}

View File

@ -231,35 +231,19 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
context("mike@hostsharing.net");
// when
final var result = rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net");
final var result = rbacUserRepository.findPermissionsOfUserByUuid(userUUID("mike@hostsharing.net"));
// then
allTheseRbacPermissionsAreReturned(result, ALL_USER_PERMISSIONS);
}
@Test
public void hostsharingAdmin_withAssumedHostmastersRole_willThrowException() {
// given
context("mike@hostsharing.net", "global#hostsharing.admin");
// when
final var result = attempt(em, () ->
rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net")
);
// then
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"[400] grantedPermissions(...) does not support assumed roles");
}
@Test
public void customerAdmin_withoutAssumedRole_canViewTheirOwnPermissions() {
// given
context("customer-admin@xxx.example.com");
// when
final var result = rbacUserRepository.findPermissionsOfUser("customer-admin@xxx.example.com");
final var result = rbacUserRepository.findPermissionsOfUserByUuid(userUUID("customer-admin@xxx.example.com"));
// then
allTheseRbacPermissionsAreReturned(
@ -299,16 +283,18 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
public void customerAdmin_withoutAssumedRole_isNotAllowedToViewGlobalAdminsPermissions() {
// given
context("customer-admin@xxx.example.com");
final UUID userUuid = userUUID("mike@hostsharing.net");
// when
final var result = attempt(em, () ->
rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net")
rbacUserRepository.findPermissionsOfUserByUuid(userUuid)
);
// then
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"[403] permissions of user \"mike@hostsharing.net\" are not accessible to user \"customer-admin@xxx.example.com\"");
"[403] permissions of user \"" + userUuid
+ "\" are not accessible to user \"customer-admin@xxx.example.com\"");
}
@Test
@ -317,7 +303,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
context("customer-admin@xxx.example.com");
// when
final var result = rbacUserRepository.findPermissionsOfUser("pac-admin-xxx00@xxx.example.com");
final var result = rbacUserRepository.findPermissionsOfUserByUuid(userUUID("pac-admin-xxx00@xxx.example.com"));
// then
allTheseRbacPermissionsAreReturned(
@ -353,7 +339,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
context("customer-admin@xxx.example.com");
// when
final var result = rbacUserRepository.findPermissionsOfUser("pac-admin-yyy00@yyy.example.com");
final var result = rbacUserRepository.findPermissionsOfUserByUuid(userUUID("pac-admin-yyy00@yyy.example.com"));
// then
noRbacPermissionsAreReturned(result);
@ -365,7 +351,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
context("pac-admin-xxx00@xxx.example.com");
// when
final var result = rbacUserRepository.findPermissionsOfUser("pac-admin-xxx00@xxx.example.com");
final var result = rbacUserRepository.findPermissionsOfUserByUuid(userUUID("pac-admin-xxx00@xxx.example.com"));
// then
allTheseRbacPermissionsAreReturned(
@ -397,6 +383,10 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
}
}
UUID userUUID(final String userName) {
return rbacUserRepository.findByName(userName).getUuid();
}
void exactlyTheseRbacUsersAreReturned(final List<RbacUserEntity> actualResult, final String... expectedUserNames) {
assertThat(actualResult)
.extracting(RbacUserEntity::getName)