add RbacUser* tests and improved http status codes
This commit is contained in:
parent
f2bc42bd85
commit
bef358eda6
@ -13,6 +13,7 @@ import org.springframework.web.context.request.WebRequest;
|
|||||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@ControllerAdvice
|
@ControllerAdvice
|
||||||
public class RestResponseEntityExceptionHandler
|
public class RestResponseEntityExceptionHandler
|
||||||
@ -22,16 +23,39 @@ public class RestResponseEntityExceptionHandler
|
|||||||
protected ResponseEntity<CustomErrorResponse> handleConflict(
|
protected ResponseEntity<CustomErrorResponse> handleConflict(
|
||||||
final RuntimeException exc, final WebRequest request) {
|
final RuntimeException exc, final WebRequest request) {
|
||||||
|
|
||||||
return new ResponseEntity<>(
|
final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
|
||||||
new CustomErrorResponse(request.getContextPath(), exc, HttpStatus.CONFLICT), HttpStatus.CONFLICT);
|
return errorResponse(request, HttpStatus.CONFLICT, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(JpaSystemException.class)
|
@ExceptionHandler(JpaSystemException.class)
|
||||||
protected ResponseEntity<CustomErrorResponse> handleJpaExceptions(
|
protected ResponseEntity<CustomErrorResponse> handleJpaExceptions(
|
||||||
final RuntimeException exc, final WebRequest request) {
|
final RuntimeException exc, final WebRequest request) {
|
||||||
|
final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
|
||||||
|
return errorResponse(request, httpStatus(message).orElse(HttpStatus.FORBIDDEN), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<HttpStatus> httpStatus(final String message) {
|
||||||
|
if (message.startsWith("ERROR: [")) {
|
||||||
|
for (HttpStatus status : HttpStatus.values()) {
|
||||||
|
if (message.startsWith("ERROR: [" + status.value() + "]")) {
|
||||||
|
return Optional.of(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.of(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResponseEntity<CustomErrorResponse> errorResponse(
|
||||||
|
final WebRequest request,
|
||||||
|
final HttpStatus conflict,
|
||||||
|
final String message) {
|
||||||
return new ResponseEntity<>(
|
return new ResponseEntity<>(
|
||||||
new CustomErrorResponse(request.getContextPath(), exc, HttpStatus.FORBIDDEN), HttpStatus.FORBIDDEN);
|
new CustomErrorResponse(request.getContextPath(), conflict, message), conflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstLine(final String message) {
|
||||||
|
return message.split("\\r|\\n|\\r\\n", 0)[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,15 +73,11 @@ class CustomErrorResponse {
|
|||||||
|
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|
||||||
public CustomErrorResponse(final String path, final RuntimeException exc, final HttpStatus status) {
|
public CustomErrorResponse(final String path, final HttpStatus status, final String message) {
|
||||||
this.timestamp = LocalDateTime.now();
|
this.timestamp = LocalDateTime.now();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.status = status.value();
|
this.status = status.value();
|
||||||
this.error = status.getReasonPhrase();
|
this.error = status.getReasonPhrase();
|
||||||
this.message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
|
this.message = message;
|
||||||
}
|
|
||||||
|
|
||||||
private String firstLine(final String message) {
|
|
||||||
return message.split("\\r|\\n|\\r\\n", 0)[0];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.rbacuser;
|
package net.hostsharing.hsadminng.rbac.rbacuser;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
import java.util.ArrayList;
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class RbacUserController {
|
public class RbacUserController {
|
||||||
@ -16,9 +21,18 @@ public class RbacUserController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RbacUserRepository rbacUserRepository;
|
private RbacUserRepository rbacUserRepository;
|
||||||
|
|
||||||
@GetMapping(value = "/api/rbacuser")
|
@GetMapping(value = "/api/rbacusers")
|
||||||
|
@Operation(description = "List accessible RBAC users with optional filter by name.",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "200",
|
||||||
|
content = @Content(array = @ArraySchema(
|
||||||
|
schema = @Schema(implementation = RbacUserEntity.class)))),
|
||||||
|
@ApiResponse(responseCode = "401",
|
||||||
|
description = "if the 'current-user' cannot be identified"),
|
||||||
|
@ApiResponse(responseCode = "403",
|
||||||
|
description = "if the 'current-user' is not allowed to assume any of the roles from 'assumed-roles'") })
|
||||||
@Transactional
|
@Transactional
|
||||||
public Iterable<RbacUserEntity> listUsers(
|
public List<RbacUserEntity> listUsers(
|
||||||
@RequestHeader(name = "current-user") String currentUserName,
|
@RequestHeader(name = "current-user") String currentUserName,
|
||||||
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles,
|
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles,
|
||||||
@RequestParam(name="name", required = false) String userName
|
@RequestParam(name="name", required = false) String userName
|
||||||
@ -31,8 +45,15 @@ public class RbacUserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/api/rbacuser/{userName}/permissions")
|
@GetMapping(value = "/api/rbacuser/{userName}/permissions")
|
||||||
|
@Operation(description = "List all visible permissions granted to the given user; reduced ", responses = {
|
||||||
|
@ApiResponse(responseCode = "200",
|
||||||
|
content = @Content(array = @ArraySchema( schema = @Schema(implementation = RbacUserPermission.class)))),
|
||||||
|
@ApiResponse(responseCode = "401",
|
||||||
|
description = "if the 'current-user' cannot be identified"),
|
||||||
|
@ApiResponse(responseCode = "403",
|
||||||
|
description = "if the 'current-user' is not allowed to view permissions of the given user") })
|
||||||
@Transactional
|
@Transactional
|
||||||
public Iterable<RbacUserPermission> listUserPermissions(
|
public List<RbacUserPermission> listUserPermissions(
|
||||||
@RequestHeader(name = "current-user") String currentUserName,
|
@RequestHeader(name = "current-user") String currentUserName,
|
||||||
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles,
|
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles,
|
||||||
@PathVariable(name= "userName") String userName
|
@PathVariable(name= "userName") String userName
|
||||||
|
@ -14,30 +14,6 @@ import java.util.UUID;
|
|||||||
@Immutable
|
@Immutable
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
//@SqlResultSetMapping(
|
|
||||||
// name = "rbacUserPermissionMapping",
|
|
||||||
// classes = {
|
|
||||||
// @ConstructorResult(
|
|
||||||
// targetClass = RbacUserPermission.class,
|
|
||||||
// columns = {
|
|
||||||
// @ColumnResult(name = "roleUuid", type = UUID.class),
|
|
||||||
// @ColumnResult(name = "oleName", type = String.class),
|
|
||||||
// @ColumnResult(name = "permissionUuid", type = UUID.class),
|
|
||||||
// @ColumnResult(name = "op", type=String.class),
|
|
||||||
// @ColumnResult(name = "objectTable", type=String.class),
|
|
||||||
// @ColumnResult(name = "objectIdName", type =String.class),
|
|
||||||
// @ColumnResult(name = "objectUuid", type = UUID.class),
|
|
||||||
// @ColumnResult(name = "campId", type = Integer.class),
|
|
||||||
// @ColumnResult(name = "userCount", type = Byte.class)
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//)
|
|
||||||
//@NamedNativeQuery(
|
|
||||||
// name = "grantedPermissions",
|
|
||||||
// query = "SELECT * FROM grantedPermissions(:userName)",
|
|
||||||
// resultSetMapping = "rbacUserPermissionMapping"
|
|
||||||
//)
|
|
||||||
public class RbacUserEntity {
|
public class RbacUserEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
@ -13,5 +13,5 @@ public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
|
|||||||
List<RbacUserEntity> findByOptionalNameLike(final String userName);
|
List<RbacUserEntity> findByOptionalNameLike(final String userName);
|
||||||
|
|
||||||
@Query(value = "SELECT * FROM grantedPermissions(:userName)", nativeQuery = true)
|
@Query(value = "SELECT * FROM grantedPermissions(:userName)", nativeQuery = true)
|
||||||
Iterable<RbacUserPermission> findPermissionsOfUser(@Param("userName") String userName);
|
List<RbacUserPermission> findPermissionsOfUser(@Param("userName") String userName);
|
||||||
}
|
}
|
||||||
|
@ -391,6 +391,19 @@ select exists(
|
|||||||
);
|
);
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
create or replace function hasGlobalRoleGranted(userUuid uuid)
|
||||||
|
returns bool
|
||||||
|
stable leakproof
|
||||||
|
language sql as $$
|
||||||
|
select exists(
|
||||||
|
select r.uuid
|
||||||
|
from RbacGrants as g
|
||||||
|
join RbacRole as r on r.uuid = g.descendantuuid
|
||||||
|
join RbacObject as o on o.uuid = r.objectuuid
|
||||||
|
where g.ascendantuuid = userUuid and o.objecttable = 'global'
|
||||||
|
);
|
||||||
|
$$;
|
||||||
|
|
||||||
create or replace procedure grantPermissionsToRole(roleUuid uuid, permissionIds uuid[])
|
create or replace procedure grantPermissionsToRole(roleUuid uuid, permissionIds uuid[])
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
begin
|
begin
|
||||||
@ -417,7 +430,7 @@ begin
|
|||||||
perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole');
|
perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole');
|
||||||
|
|
||||||
if (isGranted(subRoleId, superRoleId)) then
|
if (isGranted(subRoleId, superRoleId)) then
|
||||||
raise exception 'Cyclic role grant detected between % and %', subRoleId, superRoleId;
|
raise exception '[400] Cyclic role grant detected between % and %', subRoleId, superRoleId;
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
insert
|
insert
|
||||||
@ -487,7 +500,7 @@ begin
|
|||||||
|
|
||||||
foundRows = lastRowCount();
|
foundRows = lastRowCount();
|
||||||
if foundRows > maxObjects then
|
if foundRows > maxObjects then
|
||||||
raise exception 'Too many accessible objects, limit is %, found %.', maxObjects, foundRows
|
raise exception '[400] Too many accessible objects, limit is %, found %.', maxObjects, foundRows
|
||||||
using
|
using
|
||||||
errcode = 'P0003',
|
errcode = 'P0003',
|
||||||
hint = 'Please assume a sub-role and try again.';
|
hint = 'Please assume a sub-role and try again.';
|
||||||
|
@ -21,7 +21,7 @@ begin
|
|||||||
currentUser := null;
|
currentUser := null;
|
||||||
end;
|
end;
|
||||||
if (currentUser is null or currentUser = '') then
|
if (currentUser is null or currentUser = '') then
|
||||||
raise exception 'hsadminng.currentUser must be defined, please use "SET LOCAL ...;"';
|
raise exception '[401] hsadminng.currentUser must be defined, please use "SET LOCAL ...;"';
|
||||||
end if;
|
end if;
|
||||||
return currentUser;
|
return currentUser;
|
||||||
end; $$;
|
end; $$;
|
||||||
@ -37,7 +37,7 @@ begin
|
|||||||
currentUser := currentUser();
|
currentUser := currentUser();
|
||||||
currentUserId = (select uuid from RbacUser where name = currentUser);
|
currentUserId = (select uuid from RbacUser where name = currentUser);
|
||||||
if currentUserId is null then
|
if currentUserId is null then
|
||||||
raise exception 'hsadminng.currentUser defined as %, but does not exists', currentUser;
|
raise exception '[401] hsadminng.currentUser defined as %, but does not exists', currentUser;
|
||||||
end if;
|
end if;
|
||||||
return currentUserId;
|
return currentUserId;
|
||||||
end; $$;
|
end; $$;
|
||||||
@ -150,7 +150,7 @@ declare
|
|||||||
begin
|
begin
|
||||||
currentUserId := currentUserId();
|
currentUserId := currentUserId();
|
||||||
if currentUserId is null then
|
if currentUserId is null then
|
||||||
raise exception 'user % does not exist', currentUser();
|
raise exception '[401] user % does not exist', currentUser();
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
roleNames := assumedRoles();
|
roleNames := assumedRoles();
|
||||||
@ -176,7 +176,7 @@ begin
|
|||||||
and r.roleType = roleTypeToAssume
|
and r.roleType = roleTypeToAssume
|
||||||
into roleUuidToAssume;
|
into roleUuidToAssume;
|
||||||
if (not isGranted(currentUserId, roleUuidToAssume)) then
|
if (not isGranted(currentUserId, roleUuidToAssume)) then
|
||||||
raise exception 'user % (%) has no permission to assume role % (%)', currentUser(), currentUserId, roleName, roleUuidToAssume;
|
raise exception '[403] user % (%) has no permission to assume role % (%)', currentUser(), currentUserId, roleName, roleUuidToAssume;
|
||||||
end if;
|
end if;
|
||||||
roleIdsToAssume := roleIdsToAssume || roleUuidToAssume;
|
roleIdsToAssume := roleIdsToAssume || roleUuidToAssume;
|
||||||
end loop;
|
end loop;
|
||||||
|
@ -61,31 +61,45 @@ grant all privileges on RbacOwnGrantedPermissions_rv to restricted;
|
|||||||
/*
|
/*
|
||||||
Returns all permissions granted to the given user,
|
Returns all permissions granted to the given user,
|
||||||
which are also visible to the current user or assumed roles.
|
which are also visible to the current user or assumed roles.
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
create or replace function grantedPermissions(userName varchar)
|
create or replace function grantedPermissions(userName varchar)
|
||||||
returns table(roleUuid uuid, roleName text, permissionUuid uuid, op RbacOp, objectTable varchar, objectIdName varchar, objectUuid uuid)
|
returns table(roleUuid uuid, roleName text, permissionUuid uuid, op RbacOp, objectTable varchar, objectIdName varchar, objectUuid uuid)
|
||||||
returns null on null input
|
returns null on null input
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
targetUserId uuid;
|
||||||
|
currentUserId uuid;
|
||||||
begin
|
begin
|
||||||
-- @formatter:off
|
-- @formatter:off
|
||||||
if cardinality(assumedRoles()) > 0 then
|
if cardinality(assumedRoles()) > 0 then
|
||||||
raise exception 'grantedPermissions(...) does not support assumed roles';
|
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();
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
return query select
|
return query select
|
||||||
xp.roleUuid,
|
xp.roleUuid,
|
||||||
(xp.objecttable || '#' || xp.objectidname || '.' || xp.roletype) as roleName,
|
(xp.roleObjectTable || '#' || xp.roleObjectIdName || '.' || xp.roleType) as roleName,
|
||||||
xp.permissionUuid, xp.op, xp.objecttable, xp.objectIdName, xp.objectuuid
|
xp.permissionUuid, xp.op, xp.permissionObjectTable, xp.permissionObjectIdName, xp.permissionObjectUuid
|
||||||
from (select
|
from (select
|
||||||
r.uuid as roleUuid, r.roletype,
|
r.uuid as roleUuid, r.roletype, ro.objectTable as roleObjectTable,
|
||||||
p.uuid as permissionUuid, p.op, o.objecttable,
|
findIdNameByObjectUuid(ro.objectTable, ro.uuid) as roleObjectIdName,
|
||||||
findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName,
|
p.uuid as permissionUuid, p.op, po.objecttable as permissionObjectTable,
|
||||||
o.uuid as objectuuid
|
findIdNameByObjectUuid(po.objectTable, po.uuid) as permissionObjectIdName,
|
||||||
from queryPermissionsGrantedToSubjectId( findRbacUserId(userName)) p
|
po.uuid as permissionObjectUuid
|
||||||
join rbacgrants g on g.descendantuuid = p.uuid
|
from queryPermissionsGrantedToSubjectId( targetUserId) as p
|
||||||
join rbacobject o on o.uuid = p.objectuuid
|
join rbacgrants as g on g.descendantUuid = p.uuid
|
||||||
join rbacrole r on r.uuid = g.ascendantuuid
|
join rbacobject as po on po.uuid = p.objectUuid
|
||||||
where isGranted(currentUserId(), r.uuid)
|
join rbacrole_rv as r on r.uuid = g.ascendantUuid
|
||||||
|
join rbacobject as ro on ro.uuid = r.objectUuid
|
||||||
|
where isGranted(targetUserId, r.uuid)
|
||||||
) xp;
|
) xp;
|
||||||
-- @formatter:on
|
-- @formatter:on
|
||||||
end; $$;
|
end; $$;
|
||||||
|
@ -220,7 +220,7 @@ create or replace function addCustomerNotAllowedForCurrentSubjects()
|
|||||||
language PLPGSQL
|
language PLPGSQL
|
||||||
as $$
|
as $$
|
||||||
begin
|
begin
|
||||||
raise exception 'add-customer not permitted for %', array_to_string(currentSubjects(), ';', 'null');
|
raise exception '[403] add-customer not permitted for %', array_to_string(currentSubjects(), ';', 'null');
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,7 @@ create or replace procedure createPackageTestData(
|
|||||||
pacName varchar;
|
pacName varchar;
|
||||||
currentTask varchar;
|
currentTask varchar;
|
||||||
custAdmin varchar;
|
custAdmin varchar;
|
||||||
|
pac package;
|
||||||
begin
|
begin
|
||||||
set hsadminng.currentUser to '';
|
set hsadminng.currentUser to '';
|
||||||
|
|
||||||
@ -37,7 +38,13 @@ create or replace procedure createPackageTestData(
|
|||||||
|
|
||||||
insert
|
insert
|
||||||
into package (name, customerUuid)
|
into package (name, customerUuid)
|
||||||
values (pacName, cust.uuid);
|
values (pacName, cust.uuid)
|
||||||
|
returning * into pac;
|
||||||
|
|
||||||
|
call grantRoleToUser(
|
||||||
|
findRoleId(packageAdmin(pac)),
|
||||||
|
createRbacUser(pacName || '@' || cust.prefix || '.example.com'));
|
||||||
|
|
||||||
end loop;
|
end loop;
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ class CustomerRepositoryIntegrationTest {
|
|||||||
// then
|
// then
|
||||||
attempt.assertExceptionWithRootCauseMessage(
|
attempt.assertExceptionWithRootCauseMessage(
|
||||||
JpaSystemException.class,
|
JpaSystemException.class,
|
||||||
"user admin@aaa.example.com .* has no permission to assume role package#aab00#admin");
|
"[403] user admin@aaa.example.com", "has no permission to assume role package#aab00#admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.rbacrole;
|
package net.hostsharing.hsadminng.rbac.rbacrole;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.test.Array;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -29,7 +30,7 @@ class RbacRoleRepositoryIntegrationTest {
|
|||||||
@Nested
|
@Nested
|
||||||
class FindAllRbacRoles {
|
class FindAllRbacRoles {
|
||||||
|
|
||||||
private static final String[] ALL_TEST_DATA_ROLES = new String[] {
|
private static final String[] ALL_TEST_DATA_ROLES = Array.of(
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
"global#hostsharing.admin",
|
"global#hostsharing.admin",
|
||||||
"customer#aaa.admin", "customer#aaa.owner", "customer#aaa.tenant",
|
"customer#aaa.admin", "customer#aaa.owner", "customer#aaa.tenant",
|
||||||
@ -45,7 +46,7 @@ class RbacRoleRepositoryIntegrationTest {
|
|||||||
"package#aac01.admin", "package#aac01.owner", "package#aac01.tenant",
|
"package#aac01.admin", "package#aac01.owner", "package#aac01.tenant",
|
||||||
"package#aac02.admin", "package#aac02.owner", "package#aac02.tenant"
|
"package#aac02.admin", "package#aac02.owner", "package#aac02.tenant"
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
};
|
);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hostsharingAdmin_withoutAssumedRole_canViewAllRbacRoles() {
|
public void hostsharingAdmin_withoutAssumedRole_canViewAllRbacRoles() {
|
||||||
@ -116,7 +117,7 @@ class RbacRoleRepositoryIntegrationTest {
|
|||||||
// then
|
// then
|
||||||
attempt.assertExceptionWithRootCauseMessage(
|
attempt.assertExceptionWithRootCauseMessage(
|
||||||
JpaSystemException.class,
|
JpaSystemException.class,
|
||||||
"user admin@aaa.example.com .* has no permission to assume role package#aab00#admin");
|
"[403] user admin@aaa.example.com", "has no permission to assume role package#aab00#admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -159,11 +160,10 @@ class RbacRoleRepositoryIntegrationTest {
|
|||||||
assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";"));
|
assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void exactlyTheseRbacRolesAreReturned(final Iterable<RbacRoleEntity> actualResult, final String... rbacRoleNames) {
|
void exactlyTheseRbacRolesAreReturned(final Iterable<RbacRoleEntity> actualResult, final String... expectedRoleNames) {
|
||||||
assertThat(actualResult)
|
assertThat(actualResult)
|
||||||
//.hasSize(rbacRoleNames.length)
|
|
||||||
.extracting(RbacRoleEntity::getRoleName)
|
.extracting(RbacRoleEntity::getRoleName)
|
||||||
.containsExactlyInAnyOrder(rbacRoleNames);
|
.containsExactlyInAnyOrder(expectedRoleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacuser;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
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.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacrole.TestRbacRole.*;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacuser.TestRbacUser.userAaa;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacuser.TestRbacUser.userBbb;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@WebMvcTest(RbacUserController.class)
|
||||||
|
class RbacUserControllerRestTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MockMvc mockMvc;
|
||||||
|
@MockBean
|
||||||
|
Context contextMock;
|
||||||
|
@MockBean
|
||||||
|
RbacUserRepository rbacUserRepository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void willListAllUsers() throws Exception {
|
||||||
|
|
||||||
|
// given
|
||||||
|
when(rbacUserRepository.findByOptionalNameLike(null)).thenReturn(
|
||||||
|
List.of(userAaa, userBbb));
|
||||||
|
|
||||||
|
// when
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders
|
||||||
|
.get("/api/rbacusers")
|
||||||
|
.header("current-user", "mike@hostsharing.net")
|
||||||
|
.accept(MediaType.APPLICATION_JSON))
|
||||||
|
|
||||||
|
// then
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$", hasSize(2)))
|
||||||
|
.andExpect(jsonPath("$[0].name", is(userAaa.getName())))
|
||||||
|
.andExpect(jsonPath("$[1].name", is(userBbb.getName())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void willListUsersByName() throws Exception {
|
||||||
|
|
||||||
|
// given
|
||||||
|
when(rbacUserRepository.findByOptionalNameLike("admin@aaa")).thenReturn(
|
||||||
|
List.of(userAaa));
|
||||||
|
|
||||||
|
// when
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders
|
||||||
|
.get("/api/rbacusers")
|
||||||
|
.param("name", "admin@aaa")
|
||||||
|
.header("current-user", "mike@hostsharing.net")
|
||||||
|
.accept(MediaType.APPLICATION_JSON))
|
||||||
|
|
||||||
|
// then
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$", hasSize(1)))
|
||||||
|
.andExpect(jsonPath("$[0].name", is(userAaa.getName())));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,315 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacuser;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.test.Array;
|
||||||
|
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.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@DataJpaTest
|
||||||
|
@ComponentScan(basePackageClasses = { Context.class, RbacUserRepository.class })
|
||||||
|
class RbacUserRepositoryIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Context context;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacUserRepository rbacUserRepository;
|
||||||
|
|
||||||
|
@Autowired EntityManager em;
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindByOptionalNameLike {
|
||||||
|
|
||||||
|
private static final String[] ALL_TEST_DATA_USERS = Array.of(
|
||||||
|
// @formatter:off
|
||||||
|
"mike@hostsharing.net", "sven@hostsharing.net",
|
||||||
|
"admin@aaa.example.com",
|
||||||
|
"aaa00@aaa.example.com", "aaa01@aaa.example.com", "aaa02@aaa.example.com",
|
||||||
|
"admin@aab.example.com",
|
||||||
|
"aab00@aab.example.com", "aab01@aab.example.com", "aab02@aab.example.com",
|
||||||
|
"admin@aac.example.com",
|
||||||
|
"aac00@aac.example.com", "aac01@aac.example.com", "aac02@aac.example.com"
|
||||||
|
// @formatter:on
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hostsharingAdmin_withoutAssumedRole_canViewAllRbacUsers() {
|
||||||
|
// given
|
||||||
|
currentUser("mike@hostsharing.net");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacUserRepository.findByOptionalNameLike(null);
|
||||||
|
|
||||||
|
// then
|
||||||
|
exactlyTheseRbacUsersAreReturned(result, ALL_TEST_DATA_USERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hostsharingAdmin_withAssumedHostsharingAdminRole_canViewAllRbacUsers() {
|
||||||
|
given:
|
||||||
|
currentUser("mike@hostsharing.net");
|
||||||
|
assumedRoles("global#hostsharing.admin");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacUserRepository.findByOptionalNameLike(null);
|
||||||
|
|
||||||
|
then:
|
||||||
|
exactlyTheseRbacUsersAreReturned(result, ALL_TEST_DATA_USERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hostsharingAdmin_withAssumedCustomerAdminRole_canViewOnlyUsersHavingRolesInThatCustomersRealm() {
|
||||||
|
given:
|
||||||
|
currentUser("mike@hostsharing.net");
|
||||||
|
assumedRoles("customer#aaa.admin");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacUserRepository.findByOptionalNameLike(null);
|
||||||
|
|
||||||
|
then:
|
||||||
|
exactlyTheseRbacUsersAreReturned(
|
||||||
|
result,
|
||||||
|
"admin@aaa.example.com",
|
||||||
|
"aaa00@aaa.example.com", "aaa01@aaa.example.com", "aaa02@aaa.example.com"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void customerAdmin_withoutAssumedRole_canViewOnlyUsersHavingRolesInThatCustomersRealm() {
|
||||||
|
// given:
|
||||||
|
currentUser("admin@aaa.example.com");
|
||||||
|
|
||||||
|
// when:
|
||||||
|
final var result = rbacUserRepository.findByOptionalNameLike(null);
|
||||||
|
|
||||||
|
// then:
|
||||||
|
exactlyTheseRbacUsersAreReturned(
|
||||||
|
result,
|
||||||
|
"admin@aaa.example.com",
|
||||||
|
"aaa00@aaa.example.com", "aaa01@aaa.example.com", "aaa02@aaa.example.com"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void customerAdmin_withAssumedOwnedPackageAdminRole_canViewOnlyUsersHavingRolesInThatPackage() {
|
||||||
|
currentUser("admin@aaa.example.com");
|
||||||
|
assumedRoles("package#aaa00.admin");
|
||||||
|
|
||||||
|
final var result = rbacUserRepository.findByOptionalNameLike(null);
|
||||||
|
|
||||||
|
exactlyTheseRbacUsersAreReturned(result, "aaa00@aaa.example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void packageAdmin_withoutAssumedRole_canViewOnlyUsersHavingRolesInThatPackage() {
|
||||||
|
currentUser("aaa00@aaa.example.com");
|
||||||
|
|
||||||
|
final var result = rbacUserRepository.findByOptionalNameLike(null);
|
||||||
|
|
||||||
|
exactlyTheseRbacUsersAreReturned(result, "aaa00@aaa.example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ListUserPermissions {
|
||||||
|
|
||||||
|
private static final String[] ALL_USER_PERMISSIONS = Array.of(
|
||||||
|
// @formatter:off
|
||||||
|
"global#hostsharing.admin -> global#hostsharing: add-customer",
|
||||||
|
|
||||||
|
"customer#aaa.admin -> customer#aaa: add-package",
|
||||||
|
"customer#aaa.admin -> customer#aaa: view",
|
||||||
|
"customer#aaa.owner -> customer#aaa: *",
|
||||||
|
"customer#aaa.tenant -> customer#aaa: view",
|
||||||
|
"package#aaa00.admin -> package#aaa00: add-domain",
|
||||||
|
"package#aaa00.admin -> package#aaa00: add-unixuser",
|
||||||
|
"package#aaa00.tenant -> package#aaa00: view",
|
||||||
|
"package#aaa01.admin -> package#aaa01: add-domain",
|
||||||
|
"package#aaa01.admin -> package#aaa01: add-unixuser",
|
||||||
|
"package#aaa01.tenant -> package#aaa01: view",
|
||||||
|
"package#aaa02.admin -> package#aaa02: add-domain",
|
||||||
|
"package#aaa02.admin -> package#aaa02: add-unixuser",
|
||||||
|
"package#aaa02.tenant -> package#aaa02: view",
|
||||||
|
|
||||||
|
"customer#aab.admin -> customer#aab: add-package",
|
||||||
|
"customer#aab.admin -> customer#aab: view",
|
||||||
|
"customer#aab.owner -> customer#aab: *",
|
||||||
|
"customer#aab.tenant -> customer#aab: view",
|
||||||
|
"package#aab00.admin -> package#aab00: add-domain",
|
||||||
|
"package#aab00.admin -> package#aab00: add-unixuser",
|
||||||
|
"package#aab00.tenant -> package#aab00: view",
|
||||||
|
"package#aab01.admin -> package#aab01: add-domain",
|
||||||
|
"package#aab01.admin -> package#aab01: add-unixuser",
|
||||||
|
"package#aab01.tenant -> package#aab01: view",
|
||||||
|
"package#aab02.admin -> package#aab02: add-domain",
|
||||||
|
"package#aab02.admin -> package#aab02: add-unixuser",
|
||||||
|
"package#aab02.tenant -> package#aab02: view",
|
||||||
|
|
||||||
|
"customer#aac.admin -> customer#aac: add-package",
|
||||||
|
"customer#aac.admin -> customer#aac: view",
|
||||||
|
"customer#aac.owner -> customer#aac: *",
|
||||||
|
"customer#aac.tenant -> customer#aac: view",
|
||||||
|
"package#aac00.admin -> package#aac00: add-domain",
|
||||||
|
"package#aac00.admin -> package#aac00: add-unixuser",
|
||||||
|
"package#aac00.tenant -> package#aac00: view",
|
||||||
|
"package#aac01.admin -> package#aac01: add-domain",
|
||||||
|
"package#aac01.admin -> package#aac01: add-unixuser",
|
||||||
|
"package#aac01.tenant -> package#aac01: view",
|
||||||
|
"package#aac02.admin -> package#aac02: add-domain",
|
||||||
|
"package#aac02.admin -> package#aac02: add-unixuser",
|
||||||
|
"package#aac02.tenant -> package#aac02: view"
|
||||||
|
// @formatter:on
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hostsharingAdmin_withoutAssumedRole_canViewTheirOwnPermissions() {
|
||||||
|
// given
|
||||||
|
currentUser("mike@hostsharing.net");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net");
|
||||||
|
|
||||||
|
// then
|
||||||
|
exactlyTheseRbacPermissionsAreReturned(result, ALL_USER_PERMISSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hostsharingAdmin_withAssumedHostmastersRole_willThrowException() {
|
||||||
|
// given
|
||||||
|
currentUser("mike@hostsharing.net");
|
||||||
|
assumedRoles("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
|
||||||
|
currentUser("admin@aaa.example.com");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacUserRepository.findPermissionsOfUser("admin@aaa.example.com");
|
||||||
|
|
||||||
|
// then
|
||||||
|
exactlyTheseRbacPermissionsAreReturned(result,
|
||||||
|
// @formatter:off
|
||||||
|
"customer#aaa.admin -> customer#aaa: add-package",
|
||||||
|
"customer#aaa.admin -> customer#aaa: view",
|
||||||
|
"customer#aaa.tenant -> customer#aaa: view",
|
||||||
|
|
||||||
|
"package#aaa00.admin -> package#aaa00: add-domain",
|
||||||
|
"package#aaa00.admin -> package#aaa00: add-unixuser",
|
||||||
|
"package#aaa00.tenant -> package#aaa00: view",
|
||||||
|
|
||||||
|
"package#aaa01.admin -> package#aaa01: add-domain",
|
||||||
|
"package#aaa01.admin -> package#aaa01: add-unixuser",
|
||||||
|
"package#aaa01.tenant -> package#aaa01: view",
|
||||||
|
|
||||||
|
"package#aaa02.admin -> package#aaa02: add-domain",
|
||||||
|
"package#aaa02.admin -> package#aaa02: add-unixuser",
|
||||||
|
"package#aaa02.tenant -> package#aaa02: view"
|
||||||
|
// @formatter:on
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void customerAdmin_withoutAssumedRole_isNotAllowedToViewGlobalAdminsPermissions() {
|
||||||
|
// given
|
||||||
|
currentUser("admin@aaa.example.com");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = attempt(em, () ->
|
||||||
|
rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net")
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
result.assertExceptionWithRootCauseMessage(
|
||||||
|
JpaSystemException.class,
|
||||||
|
"[403] permissions of user \"mike@hostsharing.net\" are not accessible to user \"admin@aaa.example.com\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void customerAdmin_withoutAssumedRole_canViewAllPermissionsWithinThePacketsRealm() {
|
||||||
|
// given
|
||||||
|
currentUser("admin@aaa.example.com");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacUserRepository.findPermissionsOfUser("aaa00@aaa.example.com");
|
||||||
|
|
||||||
|
// then
|
||||||
|
exactlyTheseRbacPermissionsAreReturned(result,
|
||||||
|
// @formatter:off
|
||||||
|
"customer#aaa.tenant -> customer#aaa: view",
|
||||||
|
// "customer#aaa.admin -> customer#aaa: view" - Not permissions through the customer admin!
|
||||||
|
"package#aaa00.admin -> package#aaa00: add-unixuser",
|
||||||
|
"package#aaa00.admin -> package#aaa00: add-domain",
|
||||||
|
"package#aaa00.tenant -> package#aaa00: view"
|
||||||
|
// @formatter:on
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void packetAdmin_withoutAssumedRole_canViewAllPermissionsWithinThePacketsRealm() {
|
||||||
|
// given
|
||||||
|
currentUser("aaa00@aaa.example.com");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacUserRepository.findPermissionsOfUser("aaa00@aaa.example.com");
|
||||||
|
|
||||||
|
// then
|
||||||
|
exactlyTheseRbacPermissionsAreReturned(result,
|
||||||
|
// @formatter:off
|
||||||
|
"customer#aaa.tenant -> customer#aaa: view",
|
||||||
|
// "customer#aaa.admin -> customer#aaa: view" - Not permissions through the customer admin!
|
||||||
|
"package#aaa00.admin -> package#aaa00: add-unixuser",
|
||||||
|
"package#aaa00.admin -> package#aaa00: add-domain",
|
||||||
|
"package#aaa00.tenant -> package#aaa00: view"
|
||||||
|
// @formatter:on
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void currentUser(final String currentUser) {
|
||||||
|
context.setCurrentUser(currentUser);
|
||||||
|
assertThat(context.getCurrentUser()).as("precondition").isEqualTo(currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assumedRoles(final String assumedRoles) {
|
||||||
|
context.assumeRoles(assumedRoles);
|
||||||
|
assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void exactlyTheseRbacUsersAreReturned(final List<RbacUserEntity> actualResult, final String... expectedUserNames) {
|
||||||
|
assertThat(actualResult)
|
||||||
|
.extracting(RbacUserEntity::getName)
|
||||||
|
.containsExactlyInAnyOrder(expectedUserNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
void exactlyTheseRbacPermissionsAreReturned(
|
||||||
|
final List<RbacUserPermission> actualResult,
|
||||||
|
final String... expectedRoleNames) {
|
||||||
|
assertThat(actualResult)
|
||||||
|
.extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp())
|
||||||
|
.containsExactlyInAnyOrder(expectedRoleNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacuser;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.util.UUID.randomUUID;
|
||||||
|
|
||||||
|
public class TestRbacUser {
|
||||||
|
|
||||||
|
static final RbacUserEntity userAaa = rbacRole("admin@aaa.example.com");
|
||||||
|
static final RbacUserEntity userBbb = rbacRole("admin@bbb.example.com");
|
||||||
|
|
||||||
|
static public RbacUserEntity rbacRole(final String userName) {
|
||||||
|
return new RbacUserEntity(randomUUID(), userName);
|
||||||
|
}
|
||||||
|
}
|
13
src/test/java/net/hostsharing/test/Array.java
Normal file
13
src/test/java/net/hostsharing/test/Array.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package net.hostsharing.test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java has List.of(...), Set.of(...) and Map.of(...) all with varargs parameter,
|
||||||
|
* but no Array.of(...). Here it is.
|
||||||
|
*/
|
||||||
|
public class Array {
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static <E> E[] of(E... elements) {
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
}
|
@ -71,9 +71,11 @@ public class JpaAttempt<T> {
|
|||||||
|
|
||||||
public void assertExceptionWithRootCauseMessage(
|
public void assertExceptionWithRootCauseMessage(
|
||||||
final Class<? extends RuntimeException> expectedExceptionClass,
|
final Class<? extends RuntimeException> expectedExceptionClass,
|
||||||
final String expectedRootCauseMessage) {
|
final String... expectedRootCauseMessages) {
|
||||||
assertThat(
|
assertThat(wasSuccessful()).isFalse();
|
||||||
firstRootCauseMessageLineOf(caughtException(expectedExceptionClass)))
|
final String firstRootCauseMessageLine = firstRootCauseMessageLineOf(caughtException(expectedExceptionClass));
|
||||||
.matches(".*" + expectedRootCauseMessage + ".*");
|
for ( String expectedRootCauseMessage: expectedRootCauseMessages ) {
|
||||||
|
assertThat(firstRootCauseMessageLine).contains(expectedRootCauseMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user