improve test code coverage and generic array return from native queries

This commit is contained in:
Michael Hoennig 2022-08-30 13:08:29 +02:00
parent a1c3e95032
commit 8af93603d5
9 changed files with 71 additions and 54 deletions

View File

@ -1,16 +0,0 @@
package net.hostsharing.hsadminng.config;
import com.vladmihalcea.hibernate.type.array.UUIDArrayType;
import org.hibernate.dialect.PostgreSQL95Dialect;
import java.sql.Types;
@SuppressWarnings("unused") // configured in application.yml
public class PostgreSQL95CustomDialect extends PostgreSQL95Dialect {
public PostgreSQL95CustomDialect() {
this.registerHibernateType(Types.OTHER, "pg-uuid");
this.registerHibernateType(Types.ARRAY, UUIDArrayType.class.getName());
}
}

View File

@ -0,0 +1,15 @@
package net.hostsharing.hsadminng.config;
import org.hibernate.dialect.PostgreSQL95Dialect;
import java.sql.Types;
@SuppressWarnings("unused") // configured in application.yml
public class PostgresCustomDialect extends PostgreSQL95Dialect {
public PostgresCustomDialect() {
this.registerHibernateType(Types.OTHER, "pg-uuid");
this.registerHibernateType(Types.ARRAY, "array");
}
}

View File

@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.context; package net.hostsharing.hsadminng.context;
import com.vladmihalcea.hibernate.type.array.StringArrayType;
import com.vladmihalcea.hibernate.type.array.UUIDArrayType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -54,12 +56,12 @@ public class Context {
final String assumedRoles) { final String assumedRoles) {
final var query = em.createNativeQuery( final var query = em.createNativeQuery(
""" """
call defineContext( call defineContext(
cast(:currentTask as varchar), cast(:currentTask as varchar),
cast(:currentRequest as varchar), cast(:currentRequest as varchar),
cast(:currentUser as varchar), cast(:currentUser as varchar),
cast(:assumedRoles as varchar)); cast(:assumedRoles as varchar));
"""); """);
query.setParameter("currentTask", shortenToMaxLength(currentTask, 96)); query.setParameter("currentTask", shortenToMaxLength(currentTask, 96));
query.setParameter("currentRequest", shortenToMaxLength(currentRequest, 512)); // TODO.SPEC: length? query.setParameter("currentRequest", shortenToMaxLength(currentRequest, 512)); // TODO.SPEC: length?
query.setParameter("currentUser", currentUser); query.setParameter("currentUser", currentUser);
@ -80,11 +82,17 @@ public class Context {
} }
public String[] getAssumedRoles() { public String[] getAssumedRoles() {
return (String[]) em.createNativeQuery("select assumedRoles()").getSingleResult(); return (String[]) em.createNativeQuery("select assumedRoles() as roles")
.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("roles", StringArrayType.INSTANCE)
.getSingleResult();
} }
public UUID[] currentSubjectsUuids() { public UUID[] currentSubjectsUuids() {
return (UUID[]) em.createNativeQuery("select currentSubjectsUuids()").getSingleResult(); return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids")
.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("uuids", UUIDArrayType.INSTANCE) // TODO.BLOG
.getSingleResult();
} }
private static String getCallerMethodNameFromStack() { private static String getCallerMethodNameFromStack() {

View File

@ -16,7 +16,7 @@ spring:
jpa: jpa:
properties: properties:
hibernate: hibernate:
dialect: net.hostsharing.hsadminng.config.PostgreSQL95CustomDialect dialect: net.hostsharing.hsadminng.config.PostgresCustomDialect
liquibase: liquibase:
contexts: dev contexts: dev

View File

@ -28,6 +28,7 @@ create or replace function determineCurrentSubjectsUuids(currentUserUuid uuid, a
language plpgsql as $$ language plpgsql as $$
declare declare
roleName varchar(63); roleName varchar(63);
roleNameParts varchar(63);
objectTableToAssume varchar(63); objectTableToAssume varchar(63);
objectNameToAssume varchar(63); objectNameToAssume varchar(63);
objectUuidToAssume uuid; objectUuidToAssume uuid;
@ -48,10 +49,10 @@ begin
foreach roleName in array string_to_array(assumedRoles, ';') foreach roleName in array string_to_array(assumedRoles, ';')
loop loop
roleName = overlay(roleName placing '#' from length(roleName) + 1 - strpos(reverse(roleName), '.')); roleNameParts = overlay(roleName placing '#' from length(roleName) + 1 - strpos(reverse(roleName), '.'));
objectTableToAssume = split_part(roleName, '#', 1); objectTableToAssume = split_part(roleNameParts, '#', 1);
objectNameToAssume = split_part(roleName, '#', 2); objectNameToAssume = split_part(roleNameParts, '#', 2);
roleTypeToAssume = split_part(roleName, '#', 3); roleTypeToAssume = split_part(roleNameParts, '#', 3);
objectUuidToAssume = findObjectUuidByIdName(objectTableToAssume, objectNameToAssume); objectUuidToAssume = findObjectUuidByIdName(objectTableToAssume, objectNameToAssume);
@ -60,7 +61,10 @@ begin
where r.objectUuid = objectUuidToAssume where r.objectUuid = objectUuidToAssume
and r.roleType = roleTypeToAssume and r.roleType = roleTypeToAssume
into roleUuidToAssume; into roleUuidToAssume;
if (not isGranted(currentUserUuid, roleUuidToAssume)) then if roleUuidToAssume is null then
raise exception '[403] role % not accessible for user %', roleName, currentUser();
end if;
if not isGranted(currentUserUuid, roleUuidToAssume) then
raise exception '[403] user % has no permission to assume role %', currentUser(), roleName; raise exception '[403] user % has no permission to assume role %', currentUser(), roleName;
end if; end if;
roleIdsToAssume := roleIdsToAssume || roleUuidToAssume; roleIdsToAssume := roleIdsToAssume || roleUuidToAssume;
@ -160,7 +164,7 @@ begin
if (currentSubjectsUuids is null or length(currentSubjectsUuids) = 0 ) then if (currentSubjectsUuids is null or length(currentSubjectsUuids) = 0 ) then
currentUserName := currentUser(); currentUserName := currentUser();
if (length(currentUserName) > 0) then if (length(currentUserName) > 0) then
raise exception '[401] currentUserUuid cannot be determined, unknown user name "%"', currentUserName; raise exception '[401] currentSubjectsUuids (%) cannot be determined, unknown user name "%"', currentSubjectsUuids, currentUserName;
else else
raise exception '[401] currentSubjectsUuids cannot be determined, please call `defineContext(...)` first;"'; raise exception '[401] currentSubjectsUuids cannot be determined, please call `defineContext(...)` first;"';
end if; end if;

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.context; package net.hostsharing.hsadminng.context;
import net.hostsharing.test.Array;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
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;
@ -32,23 +33,26 @@ class ContextIntegrationTests {
context.define("mike@hostsharing.net", null); context.define("mike@hostsharing.net", null);
final var currentTask = context.getCurrentTask(); assertThat(context.getCurrentTask())
assertThat(currentTask).isEqualTo("ContextIntegrationTests.defineWithoutHttpServletRequestUsesCallStack"); .isEqualTo("ContextIntegrationTests.defineWithoutHttpServletRequestUsesCallStack");
} }
@Test @Test
@Transactional @Transactional
void defineWithCurrentUserButWithoutAssumedRoles() { void defineWithCurrentUserButWithoutAssumedRoles() {
// when
context.define("mike@hostsharing.net"); context.define("mike@hostsharing.net");
final var currentUser = context.getCurrentUser(); // then
assertThat(currentUser).isEqualTo("mike@hostsharing.net"); assertThat(context.getCurrentUser()).
isEqualTo("mike@hostsharing.net");
final var currentUserUuid = context.getCurrentUserUUid(); assertThat(context.getCurrentUserUUid()).isNotNull();
assertThat(currentUserUuid).isNotNull();
final var assumedRoleUuids = context.currentSubjectsUuids(); assertThat(context.getAssumedRoles()).isEmpty();
assertThat(assumedRoleUuids).containsExactly(currentUserUuid);
assertThat(context.currentSubjectsUuids())
.containsExactly(context.getCurrentUserUUid());
} }
@Test @Test
@ -80,13 +84,17 @@ class ContextIntegrationTests {
@Test @Test
@Transactional @Transactional
void defineWithCurrentUserAndAssumedRoles() { void defineWithCurrentUserAndAssumedRoles() {
// given
context.define("mike@hostsharing.net", "customer#xxx.owner;customer#yyy.owner"); context.define("mike@hostsharing.net", "customer#xxx.owner;customer#yyy.owner");
// when
final var currentUser = context.getCurrentUser(); final var currentUser = context.getCurrentUser();
assertThat(currentUser).isEqualTo("mike@hostsharing.net"); assertThat(currentUser).isEqualTo("mike@hostsharing.net");
final var assumedRoles = context.currentSubjectsUuids(); // then
assertThat(assumedRoles).hasSize(2); assertThat(context.getAssumedRoles())
.isEqualTo(Array.of("customer#xxx.owner", "customer#yyy.owner"));
assertThat(context.currentSubjectsUuids()).hasSize(2);
} }
@Test @Test
@ -99,6 +107,6 @@ class ContextIntegrationTests {
// then // then
result.assertExceptionWithRootCauseMessage( result.assertExceptionWithRootCauseMessage(
javax.persistence.PersistenceException.class, javax.persistence.PersistenceException.class,
"ERROR: [403] user customer-admin@xxx.example.com has no permission to assume role package#yyy00#admin"); "ERROR: [403] user customer-admin@xxx.example.com has no permission to assume role package#yyy00.admin");
} }
} }

View File

@ -5,6 +5,7 @@ import io.restassured.http.ContentType;
import io.restassured.response.ValidatableResponse; import io.restassured.response.ValidatableResponse;
import net.hostsharing.hsadminng.Accepts; import net.hostsharing.hsadminng.Accepts;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.context.ContextBasedTest; import net.hostsharing.hsadminng.context.ContextBasedTest;
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleEntity; import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleEntity;
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository;
@ -203,7 +204,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
@Test @Test
@Accepts({ "GRT:R(Read)", "GRT:X(Access Control)" }) @Accepts({ "GRT:R(Read)", "GRT:X(Access Control)" })
void packageAdmin_withAssumedPackageAdmin_canNotReadItsOwnGrantByIdAnymore() { void packageAdmin_withAssumedPackageAdmin_canStillReadItsOwnGrantById() {
// given // given
final var givenCurrentUserAsPackageAdmin = new Subject( final var givenCurrentUserAsPackageAdmin = new Subject(
"pac-admin-xxx00@xxx.example.com", "pac-admin-xxx00@xxx.example.com",
@ -225,21 +226,20 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
@Test @Test
@Accepts({ "GRT:R(Read)", "GRT:X(Access Control)" }) @Accepts({ "GRT:R(Read)", "GRT:X(Access Control)" })
void packageAdmin_withAssumedUnixUserAdmin_canNotReadItsOwnGrantByIdAnymore() { void packageAdmin_withAssumedPackageTenantRole_canNotReadItsOwnGrantByIdAnymore() {
// given // given
final var givenCurrentUserAsPackageAdmin = new Subject( final var givenCurrentUserAsPackageAdmin = new Subject(
"pac-admin-xxx00@xxx.example.com", "pac-admin-xxx00@xxx.example.com",
"unixuser#xxx00-xxxa.admin"); "package#xxx00.tenant");
final var givenGranteeUser = findRbacUserByName("pac-admin-xxx00@xxx.example.com"); final var givenGranteeUser = findRbacUserByName("pac-admin-xxx00@xxx.example.com");
final var givenGrantedRole = findRbacRoleByName("package#xxx00.admin"); final var givenGrantedRole = findRbacRoleByName("package#xxx00.admin");
// when
final var grant = givenCurrentUserAsPackageAdmin.getGrantById() final var grant = givenCurrentUserAsPackageAdmin.getGrantById()
.forGrantedRole(givenGrantedRole).toGranteeUser(givenGranteeUser); .forGrantedRole(givenGrantedRole).toGranteeUser(givenGranteeUser);
// then // then
grant.assertThat() grant.assertThat()
.statusCode(401); .statusCode(404);
} }
} }
@ -453,8 +453,6 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
private Subject currentSubject = Subject.this; private Subject currentSubject = Subject.this;
private RbacRoleEntity grantedRole; private RbacRoleEntity grantedRole;
private boolean assumed;
private RbacUserEntity granteeUser;
GetGrantByIdFixture forGrantedRole(final RbacRoleEntity grantedRole) { GetGrantByIdFixture forGrantedRole(final RbacRoleEntity grantedRole) {
this.grantedRole = grantedRole; this.grantedRole = grantedRole;
@ -462,7 +460,6 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
} }
ValidatableResponse toGranteeUser(final RbacUserEntity granteeUser) { ValidatableResponse toGranteeUser(final RbacUserEntity granteeUser) {
this.granteeUser = granteeUser;
return RestAssured // @formatter:ff return RestAssured // @formatter:ff
.given() .given()
@ -473,7 +470,8 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
.get("http://localhost/api/rbac-grants/%s/%s".formatted( .get("http://localhost/api/rbac-grants/%s/%s".formatted(
grantedRole.getUuid(), granteeUser.getUuid() grantedRole.getUuid(), granteeUser.getUuid()
)) ))
.then().log().all(); // @formatter:on .then().log().all();
// @formatter:on
} }
} }
} }

View File

@ -147,7 +147,7 @@ class RbacRoleRepositoryIntegrationTest {
result.assertExceptionWithRootCauseMessage( result.assertExceptionWithRootCauseMessage(
JpaSystemException.class, JpaSystemException.class,
"[401] currentUserUuid cannot be determined, unknown user name \"unknown@example.org\""); "[401] currentSubjectsUuids () cannot be determined, unknown user name \"unknown@example.org\"");
} }
} }

View File

@ -13,7 +13,7 @@ spring:
properties: properties:
hibernate: hibernate:
default_schema: public default_schema: public
dialect: net.hostsharing.hsadminng.config.PostgreSQL95CustomDialect dialect: net.hostsharing.hsadminng.config.PostgresCustomDialect
format_sql: false format_sql: false
hibernate: hibernate:
ddl-auto: none ddl-auto: none