add CustomerRepositoryIntegrationTest, fix testcontainers liquibase context and improve error messages

This commit is contained in:
Michael Hoennig 2022-07-31 18:56:03 +02:00
parent 5d4fb85383
commit 27c5699c36
6 changed files with 214 additions and 24 deletions

View File

@ -8,6 +8,7 @@ public class PostgreSQL95CustomDialect extends PostgreSQL95Dialect {
public PostgreSQL95CustomDialect() {
this.registerHibernateType(2003, StringArrayType.class.getName());
this.registerHibernateType(1111, "pg-uuid");
}
}

View File

@ -1,9 +1,12 @@
package net.hostsharing.hsadminng.hscustomer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
import java.util.UUID;
public interface CustomerRepository extends JpaRepository<CustomerEntity, UUID> {
List<CustomerEntity> findByPrefixLike(final String prefix);
}

View File

@ -601,8 +601,12 @@ begin
objectTable := pureIdentifier(objectTable);
objectIdName := pureIdentifier(objectIdName);
sql := format('select * from %sUuidByIdName(%L);', objectTable, objectIdName);
begin
raise notice 'sql: %', sql;
execute sql into uuid;
exception when OTHERS then
raise exception 'function %UuidByIdName(...) not found, add identity view support for table %', objectTable, objectTable;
end;
return uuid;
end; $$;
@ -622,8 +626,12 @@ declare
roleUuidToAssume uuid;
begin
currentUserId := currentUserId();
if currentUserId is null then
raise exception 'user % does not exist', currentUser();
end if;
roleNames := assumedRoles();
if (cardinality(roleNames) = 0) then
if cardinality(roleNames) = 0 then
return array [currentUserId];
end if;
@ -645,7 +653,7 @@ begin
and r.roleType = roleTypeToAssume
into roleUuidToAssume;
if (not isGranted(currentUserId, roleUuidToAssume)) then
raise exception 'user % has no permission to assume role %', currentUser(), roleUuidToAssume;
raise exception 'user % (%) has no permission to assume role % (%)', currentUser(), currentUserId, roleName, roleUuidToAssume;
end if;
roleIdsToAssume := roleIdsToAssume || roleUuidToAssume;
end loop;

View File

@ -9,19 +9,44 @@
Otherwise these columns needed to be nullable and
many queries would be more complicated.
*/
create table Hostsharing
create table Global
(
uuid uuid primary key references RbacObject (uuid)
uuid uuid primary key references RbacObject (uuid),
name varchar(63)
);
create unique index Hostsharing_Singleton on Hostsharing ((0));
create unique index Global_Singleton on Global ((0));
/**
A single row to be referenced as a global object.
*/
insert
into RbacObject (objecttable) values ('hostsharing');
into RbacObject (objecttable) values ('global');
insert
into Hostsharing (uuid) values ((select uuid from RbacObject where objectTable = 'hostsharing'));
into Global (uuid, name) values ((select uuid from RbacObject where objectTable = 'global'), 'hostsharing');
--//
-- ============================================================================
--changeset hs-base-GLOBAL-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a view to the global object table which maps the identifying name to the objectUuid.
*/
drop view if exists global_iv;
create or replace view global_iv as
select distinct target.uuid, target.name as idName
from global as target;
grant all privileges on global_iv to restricted;
/*
Returns the objectUuid for a given identifying name (in this case the prefix).
*/
create or replace function globalUuidByIdName(idName varchar)
returns uuid
language sql
strict as $$
select uuid from global_iv iv where iv.idName = globalUuidByIdName.idName;
$$;
--//
-- ============================================================================
@ -35,12 +60,12 @@ create or replace function hostsharingAdmin()
returns null on null input
stable leakproof
language sql as $$
select 'global', (select uuid from RbacObject where objectTable = 'hostsharing'), 'admin'::RbacRoleType;
select 'global', (select uuid from RbacObject where objectTable = 'global'), 'admin'::RbacRoleType;
$$;
select createRole(hostsharingAdmin());
-- ============================================================================
--changeset hs-base-ADMIN-USERS:1 context:dev endDelimiter:--//
--changeset hs-base-ADMIN-USERS:1 context:dev,test,tc endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Create two users and assign both to the administrators role.
@ -58,7 +83,7 @@ $$;
-- ============================================================================
--changeset hs-base-hostsharing-TEST:1 context:dev runAlways:true endDelimiter:--//
--changeset hs-base-hostsharing-TEST:1 context:dev,test,tc runAlways:true endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
@ -69,16 +94,16 @@ do language plpgsql $$
declare
userName varchar;
begin
set local hsadminng.currentUser = 'mike@hostsharing.net';
select userName from RbacUser where uuid = currentUserId() into userName;
if userName <> 'mike@hostsharing.net' then
raise exception 'fetching initial currentUser failed';
end if;
set local hsadminng.currentUser = 'sven@hostsharing.net';
select userName from RbacUser where uuid = currentUserId() into userName;
if userName <> 'sven@hostsharing.net' then
raise exception 'fetching changed currentUser failed';
raise exception 'setting or fetching initial currentUser failed, got: %', userName;
end if;
set local hsadminng.currentUser = 'mike@hostsharing.net';
select userName from RbacUser where uuid = currentUserId() into userName;
if userName = 'mike@hostsharing.net' then
raise exception 'currentUser should not change in one transaction, but did change, got: %', userName;
end if;
end; $$;
--//

View File

@ -0,0 +1,155 @@
package net.hostsharing.hsadminng.hscustomer;
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.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.orm.jpa.JpaSystemException;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@DataJpaTest
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
class CustomerRepositoryIntegrationTest {
final static String adminUser = "mike@hostsharing.net";
final static String customerAaa = "admin@aaa.example.com";
@Autowired
Context context;
@Autowired
CustomerRepository customerRepository;
@Autowired EntityManager em;
@Test
@Transactional
void hostsharingAdminWithoutAssumedRoleCanViewAllCustomers() {
// given
context.setCurrentUser(adminUser);
// when
final var actual = customerRepository.findAll();
// then
assertThat(actual).hasSize(3)
.extracting(CustomerEntity::getPrefix)
.containsExactlyInAnyOrder("aaa", "aab", "aac");
}
@Test
@Transactional
void hostsharingAdminWithAssumedHostsharingAdminRoleCanViewAllCustomers() {
// given
context.setCurrentUser(adminUser);
context.assumeRoles("global#hostsharing.admin");
// when
final var actual = customerRepository.findAll();
// then
assertThat(actual).hasSize(3)
.extracting(CustomerEntity::getPrefix)
.containsExactlyInAnyOrder("aaa", "aab", "aac");
}
@Test
@Transactional
void customerAdminWithoutAssumedRoleCanViewItsOwnCustomer() {
// given
context.setCurrentUser(customerAaa);
// when
final var actual = customerRepository.findAll();
// then
assertThat(actual).hasSize(1)
.extracting(CustomerEntity::getPrefix)
.containsExactly("aaa");
}
@Test
@Transactional
void customerAdminWithAssumedOwnedPackageAdminRoleCanViewItsOwnCustomer() {
// given
context.setCurrentUser(customerAaa);
context.assumeRoles("package#aaa00.admin");
// when
final var actual = customerRepository.findAll();
// then
assertThat(actual).hasSize(1)
.extracting(CustomerEntity::getPrefix)
.containsExactly("aaa");
}
@Test
@Transactional
void customerAdminWithAssumedAlienPackageAdminRoleCanViewItsOwnCustomer() {
// given
context.setCurrentUser(customerAaa);
context.assumeRoles("package#aab00.admin");
// when
final JpaSystemException thrown =
assertThrows(JpaSystemException.class, () -> customerRepository.findAll());
// then
assertThat(firstRootCauseMessageLineOf(thrown)).matches(
".* user admin@aaa.example.com .* has no permission to assume role package#aab00#admin .*"
);
}
@Test
@Transactional
void unknownUserWithoutAssumedRoleCannotViewAnyCustomers() {
// given
context.setCurrentUser("unknown@example.org");
// when
final JpaSystemException thrown =
assertThrows(JpaSystemException.class, () -> customerRepository.findAll());
// then
assertThat(firstRootCauseMessageLineOf(thrown)).matches(
".* user unknown@example.org does not exist.*"
);
}
@Test
@Transactional
void unknownUserWithAssumedRoleCannotViewAnyCustomers() {
// given
context.setCurrentUser("unknown@example.org");
assertThat(context.getCurrentUser()).isEqualTo("unknown@example.org");
context.assumeRoles("customer#aaa.admin");
// when
final JpaSystemException thrown =
assertThrows(JpaSystemException.class, () -> customerRepository.findAll());
// then
assertThat(firstRootCauseMessageLineOf(thrown)).matches(
".* user unknown@example.org does not exist.*"
);
}
private String firstRootCauseMessageLineOf(final JpaSystemException throwable) {
return Optional.ofNullable(throwable.getRootCause())
.map(Throwable::getMessage)
.map( message -> message.split("\\r|\\n|\\r\\n", 0)[0])
.orElse(null);
}
}

View File

@ -4,7 +4,8 @@ spring:
platform: postgres
datasource:
url: jdbc:tc:postgresql:12.9-alpine:///spring_boot_testcontainers
url: jdbc:tc:postgresql:13.7-bullseye:///spring_boot_testcontainers
url-local: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: password
@ -23,11 +24,8 @@ spring:
liquibase:
change-log: classpath:/db/changelog/db.changelog-master.yaml
contexts: test
contexts: tc,test,dev
logging:
level:
liquibase: INFO
liquibase:
contexts: dev,tc