introduces generateRbacRestrictedView to generate restricted view + triggers

This commit is contained in:
Michael Hoennig 2022-09-19 20:43:14 +02:00
parent 2cae17a045
commit 44eb59c918
14 changed files with 230 additions and 315 deletions

View File

@ -18,4 +18,6 @@ public interface TestCustomerRepository extends Repository<TestCustomerEntity, U
TestCustomerEntity save(final TestCustomerEntity entity); TestCustomerEntity save(final TestCustomerEntity entity);
long count(); long count();
int deleteByUuid(UUID uuid);
} }

View File

@ -22,7 +22,7 @@ begin
createDeleteTriggerSQL = format($sql$ createDeleteTriggerSQL = format($sql$
create trigger deleteRbacRulesFor_%s_Trigger create trigger deleteRbacRulesFor_%s_Trigger
before delete after delete
on %s on %s
for each row for each row
execute procedure deleteRelatedRbacObject(); execute procedure deleteRelatedRbacObject();
@ -113,3 +113,121 @@ begin
execute sql; execute sql;
end; $$; end; $$;
--// --//
-- ============================================================================
--changeset rbac-generators-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure generateRbacRestrictedView(targetTable text, orderBy text, columnUpdates text)
language plpgsql as $$
declare
sql text;
begin
/*
Creates a restricted view based on the 'view' permission of the current subject.
*/
sql := format($sql$
set session session authorization default;
create view %1$s_rv as
select target.*
from %1$s as target
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', '%1$s', currentSubjectsUuids()))
order by %2$s;
grant all privileges on %1$s_rv to restricted;
$sql$, targetTable, orderBy);
execute sql;
/**
Instead of insert trigger function for the restricted view.
*/
sql := format($sql$
create or replace function %1$sInsert()
returns trigger
language plpgsql as $f$
declare
newTargetRow %1$s;
begin
insert
into %1$s
values (new.*)
returning * into newTargetRow;
return newTargetRow;
end; $f$;
$sql$, targetTable);
execute sql;
/*
Creates an instead of insert trigger for the restricted view.
*/
sql := format($sql$
create trigger %1$sInsert_tg
instead of insert
on %1$s_rv
for each row
execute function %1$sInsert();
$sql$, targetTable);
execute sql;
/**
Instead of delete trigger function for the restricted view.
*/
sql := format($sql$
create or replace function %1$sDelete()
returns trigger
language plpgsql as $f$
begin
if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', '%1$s', currentSubjectsUuids())) then
delete from %1$s p where p.uuid = old.uuid;
return old;
end if;
raise exception '[403] Subject %% is not allowed to delete %1$s uuid %%', currentSubjectsUuids(), old.uuid;
end; $f$;
$sql$, targetTable);
execute sql;
/*
Creates an instead of delete trigger for the restricted view.
*/
sql := format($sql$
create trigger %1$sDelete_tg
instead of delete
on %1$s_rv
for each row
execute function %1$sDelete();
$sql$, targetTable);
execute sql;
/**
Instead of update trigger function for the restricted view
based on the 'edit' permission of the current subject.
*/
sql := format($sql$
create or replace function %1$sUpdate()
returns trigger
language plpgsql as $f$
begin
if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('edit', '%1$s', currentSubjectsUuids())) then
update %1$s
set %2$s
where uuid = old.uuid;
return old;
end if;
raise exception '[403] Subject %% is not allowed to update %1$s uuid %%', currentSubjectsUuids(), old.uuid;
end; $f$;
$sql$, targetTable, columnUpdates);
execute sql;
/*
Creates an instead of delete trigger for the restricted view.
*/
sql = format($sql$
create trigger %1$sUpdate_tg
instead of update
on %1$s_rv
for each row
execute function %1$sUpdate();
$sql$, targetTable);
execute sql;
end; $$;
--//

View File

@ -87,17 +87,12 @@ call generateRbacIdentityView('test_customer', $idName$
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* call generateRbacRestrictedView('test_customer', 'target.prefix',
Creates a view to the customer main table with row-level limitation $updates$
based on the 'view' permission of the current user or assumed roles. reference = new.reference,
*/ prefix = new.prefix,
set session session authorization default; adminUserName = new.adminUserName
drop view if exists test_customer_rv; $updates$);
create or replace view test_customer_rv as
select target.*
from test_customer as target
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'test_customer', currentSubjectsUuids()));
grant all privileges on test_customer_rv to restricted;
--// --//

View File

@ -38,7 +38,7 @@ begin
-- an owner role is created and assigned to the customer's admin role -- an owner role is created and assigned to the customer's admin role
packageOwnerRoleUuid = createRole( packageOwnerRoleUuid = createRole(
testPackageOwner(NEW), testPackageOwner(NEW),
withoutPermissions(), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']),
beneathRole(testCustomerAdmin(parentCustomer)) beneathRole(testCustomerAdmin(parentCustomer))
); );
@ -64,7 +64,6 @@ end; $$;
An AFTER INSERT TRIGGER which creates the role structure for a new package. An AFTER INSERT TRIGGER which creates the role structure for a new package.
*/ */
drop trigger if exists createRbacRolesForTestPackage_Trigger on test_package;
create trigger createRbacRolesForTestPackage_Trigger create trigger createRbacRolesForTestPackage_Trigger
after insert after insert
on test_package on test_package
@ -76,9 +75,7 @@ execute procedure createRbacRolesForTestPackage();
-- ============================================================================ -- ============================================================================
--changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityView('test_package', $idName$ call generateRbacIdentityView('test_package', 'target.name');
target.name
$idName$);
--// --//
@ -90,11 +87,22 @@ call generateRbacIdentityView('test_package', $idName$
Creates a view to the customer main table which maps the identifying name Creates a view to the customer main table which maps the identifying name
(in this case, the prefix) to the objectUuid. (in this case, the prefix) to the objectUuid.
*/ */
drop view if exists test_package_rv; -- drop view if exists test_package_rv;
create or replace view test_package_rv as -- create or replace view test_package_rv as
select target.* -- select target.*
from test_package as target -- from test_package as target
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'test_package', currentSubjectsUuids())) -- where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'test_package', currentSubjectsUuids()))
order by target.name; -- order by target.name;
grant all privileges on test_package_rv to restricted; -- grant all privileges on test_package_rv to restricted;
call generateRbacRestrictedView('test_package', 'target.name',
$updates$
version = new.version,
customerUuid = new.customerUuid,
name = new.name,
description = new.description
$updates$);
--// --//

View File

@ -31,7 +31,7 @@ begin
insert insert
into test_package (customerUuid, name, description) into test_package (customerUuid, name, description)
values (cust.uuid, pacName, 'Here can add your own description of package ' || pacName || '.') values (cust.uuid, pacName, 'Here you can add your own description of package ' || pacName || '.')
returning * into pac; returning * into pac;
call grantRoleToUser( call grantRoleToUser(

View File

@ -100,7 +100,7 @@ call generateRbacIdentityView('test_domain', $idName$
-- ============================================================================ -- ============================================================================
--changeset test-package-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*

View File

@ -86,82 +86,16 @@ call generateRbacIdentityView('hs_office_contact', $idName$
-- ============================================================================ -- ============================================================================
--changeset hs-office-contact-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-contact-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* call generateRbacRestrictedView('hs_office_contact', 'target.label',
Creates a view to the contact main table with row-level limitation $updates$
based on the 'view' permission of the current user or assumed roles. label = new.label,
*/ postalAddress = new.postalAddress,
set session session authorization default; emailAddresses = new.emailAddresses,
drop view if exists hs_office_contact_rv; phoneNumbers = new.phoneNumbers
create or replace view hs_office_contact_rv as $updates$);
select target.*
from hs_office_contact as target
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'hs_office_contact', currentSubjectsUuids()));
grant all privileges on hs_office_contact_rv to restricted;
--//
-- ============================================================================
--changeset hs-office-contact-rbac-INSTEAD-OF-INSERT-TRIGGER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Instead of insert trigger function for hs_office_contact_rv.
*/
create or replace function insertHsOfficeContact()
returns trigger
language plpgsql as $$
declare
newUser hs_office_contact;
begin
insert
into hs_office_contact
values (new.*)
returning * into newUser;
return newUser;
end;
$$;
/*
Creates an instead of insert trigger for the hs_office_contact_rv view.
*/
create trigger insertHsOfficeContact_Trigger
instead of insert
on hs_office_contact_rv
for each row
execute function insertHsOfficeContact();
--//
-- ============================================================================
--changeset hs-office-contact-rbac-INSTEAD-OF-DELETE-TRIGGER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Instead of delete trigger function for hs_office_contact_rv.
Checks if the current subject (user / assumed role) has the permission to delete the row.
*/
create or replace function deleteHsOfficeContact()
returns trigger
language plpgsql as $$
begin
if hasGlobalRoleGranted(currentUserUuid()) or
old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', 'hs_office_contact', currentSubjectsUuids())) then
delete from hs_office_contact c where c.uuid = old.uuid;
return old;
end if;
raise exception '[403] User % not allowed to delete contact uuid %', currentUser(), old.uuid;
end; $$;
/*
Creates an instead of delete trigger for the hs_office_contact_rv view.
*/
create trigger deleteHsOfficeContact_Trigger
instead of delete
on hs_office_contact_rv
for each row
execute function deleteHsOfficeContact();
--/ --/
-- ============================================================================ -- ============================================================================
--changeset hs-office-contact-rbac-NEW-CONTACT:1 endDelimiter:--// --changeset hs-office-contact-rbac-NEW-CONTACT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -17,11 +17,9 @@ call generateRbacRoleDescriptors('hsOfficePerson', 'hs_office_person');
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-ROLES-CREATION:1 endDelimiter:--// --changeset hs-office-person-rbac-ROLES-CREATION:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Creates the roles and their assignments for a new person for the AFTER INSERT TRIGGER. Creates the roles and their assignments for a new person for the AFTER INSERT TRIGGER.
*/ */
create or replace function createRbacRolesForHsOfficePerson() create or replace function createRbacRolesForHsOfficePerson()
returns trigger returns trigger
language plpgsql language plpgsql
@ -85,82 +83,16 @@ call generateRbacIdentityView('hs_office_person', $idName$
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-person-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* call generateRbacRestrictedView('hs_office_person', 'concat(target.tradeName, target.familyName, target.givenName)',
Creates a view to the person main table with row-level limitation $updates$
based on the 'view' permission of the current user or assumed roles. personType = new.personType,
*/ tradeName = new.tradeName,
set session session authorization default; givenName = new.givenName,
drop view if exists hs_office_person_rv; familyName = new.familyName
create or replace view hs_office_person_rv as $updates$);
select target.*
from hs_office_person as target
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'hs_office_person', currentSubjectsUuids()));
grant all privileges on hs_office_person_rv to restricted;
--// --//
-- ============================================================================
--changeset hs-office-person-rbac-INSTEAD-OF-INSERT-TRIGGER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Instead of insert trigger function for hs_office_person_rv.
*/
create or replace function insertHsOfficePerson()
returns trigger
language plpgsql as $$
declare
newUser hs_office_person;
begin
insert
into hs_office_person
values (new.*)
returning * into newUser;
return newUser;
end;
$$;
/*
Creates an instead of insert trigger for the hs_office_person_rv view.
*/
create trigger insertHsOfficePerson_Trigger
instead of insert
on hs_office_person_rv
for each row
execute function insertHsOfficePerson();
--//
-- ============================================================================
--changeset hs-office-person-rbac-INSTEAD-OF-DELETE-TRIGGER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Instead of delete trigger function for hs_office_person_rv.
Checks if the current subject (user / assumed role) has the permission to delete the row.
*/
create or replace function deleteHsOfficePerson()
returns trigger
language plpgsql as $$
begin
if hasGlobalRoleGranted(currentUserUuid()) or
old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', 'hs_office_person', currentSubjectsUuids())) then
delete from hs_office_person c where c.uuid = old.uuid;
return old;
end if;
raise exception '[403] User % not allowed to delete person uuid %', currentUser(), old.uuid;
end; $$;
/*
Creates an instead of delete trigger for the hs_office_person_rv view.
*/
create trigger deleteHsOfficePerson_Trigger
instead of delete
on hs_office_person_rv
for each row
execute function deleteHsOfficePerson();
--/
-- ============================================================================ -- ============================================================================
--changeset hs-office-person-rbac-NEW-PERSON:1 endDelimiter:--// --changeset hs-office-person-rbac-NEW-PERSON:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -127,122 +127,20 @@ call generateRbacIdentityView('hs_office_partner', $idName$
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-partner-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* call generateRbacRestrictedView('hs_office_partner',
Creates a view to the partner main table with row-level limitation '(select idName from hs_office_person_iv p where p.uuid = target.personUuid)',
based on the 'view' permission of the current user or assumed roles. $updates$
*/ personUuid = new.personUuid,
set session session authorization default; contactUuid = new.contactUuid,
drop view if exists hs_office_partner_rv; registrationOffice = new.registrationOffice,
create or replace view hs_office_partner_rv as registrationNumber = new.registrationNumber,
select target.* birthday = new.birthday,
from hs_office_partner as target birthName = new.birthName,
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'hs_office_partner', currentSubjectsUuids())); dateOfDeath = new.dateOfDeath
grant all privileges on hs_office_partner_rv to restricted; $updates$);
--// --//
-- ============================================================================
--changeset hs-office-partner-rbac-INSTEAD-OF-INSERT-TRIGGER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Instead of insert trigger function for hs_office_partner_rv.
*/
create or replace function insertHsOfficePartner()
returns trigger
language plpgsql as $$
declare
newUser hs_office_partner;
begin
insert
into hs_office_partner
values (new.*)
returning * into newUser;
return newUser;
end;
$$;
/*
Creates an instead of insert trigger for the hs_office_partner_rv view.
*/
create trigger insertHsOfficePartner_Trigger
instead of insert
on hs_office_partner_rv
for each row
execute function insertHsOfficePartner();
--//
-- ============================================================================
--changeset hs-office-partner-rbac-INSTEAD-OF-DELETE-TRIGGER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Instead of delete trigger function for hs_office_partner_rv.
Checks if the current subject (user / assumed role) has the permission to delete the row.
*/
create or replace function deleteHsOfficePartner()
returns trigger
language plpgsql as $$
begin
if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', 'hs_office_partner', currentSubjectsUuids())) then
delete from hs_office_partner p where p.uuid = old.uuid;
return old;
end if;
raise exception '[403] Subject % is not allowed to delete partner uuid %', currentSubjectsUuids(), old.uuid;
end; $$;
/*
Creates an instead of delete trigger for the hs_office_partner_rv view.
*/
create trigger deleteHsOfficePartner_Trigger
instead of delete
on hs_office_partner_rv
for each row
execute function deleteHsOfficePartner();
--/
-- ============================================================================
--changeset hs-office-partner-rbac-INSTEAD-OF-UPDATE-TRIGGER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/**
Instead of update trigger function for hs_office_partner_rv.
Checks if the current subject (user / assumed role) has the permission to update the row.
*/
create or replace function updateHsOfficePartner()
returns trigger
language plpgsql as $$
begin
if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('edit', 'hs_office_partner', currentSubjectsUuids())) then
update hs_office_partner
set personUuid = new.personUuid,
contactUuid = new.contactUuid,
registrationOffice = new.registrationOffice,
registrationNumber = new.registrationNumber,
birthday = new.birthday,
birthName = new.birthName,
dateOfDeath = new.dateOfDeath
where uuid = old.uuid;
return old;
end if;
raise exception '[403] Subject % is not allowed to update partner uuid %', currentSubjectsUuids(), old.uuid;
end; $$;
/*
Creates an instead of delete trigger for the hs_office_partner_rv view.
*/
create trigger updateHsOfficePartner_Trigger
instead of update
on hs_office_partner_rv
for each row
execute function updateHsOfficePartner();
--/
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-rbac-NEW-CONTACT:1 endDelimiter:--// --changeset hs-office-partner-rbac-NEW-CONTACT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -422,11 +422,6 @@ class HsOfficePartnerControllerAcceptanceTest {
} }
} }
private UUID toCleanup(final UUID tempPartnerUuid) {
tempPartnerUuids.add(tempPartnerUuid);
return tempPartnerUuid;
}
private HsOfficePartnerEntity givenSomeTemporaryPartnerBessler() { private HsOfficePartnerEntity givenSomeTemporaryPartnerBessler() {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
@ -444,6 +439,11 @@ class HsOfficePartnerControllerAcceptanceTest {
}).assertSuccessful().returnedValue(); }).assertSuccessful().returnedValue();
} }
private UUID toCleanup(final UUID tempPartnerUuid) {
tempPartnerUuids.add(tempPartnerUuid);
return tempPartnerUuid;
}
@AfterEach @AfterEach
void cleanup() { void cleanup() {
tempPartnerUuids.forEach(uuid -> { tempPartnerUuids.forEach(uuid -> {

View File

@ -243,7 +243,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
// then // then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class, result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"[403] Subject ", " is not allowed to update partner uuid"); "[403] Subject ", " is not allowed to update hs_office_partner uuid");
} }
@Test @Test
@ -265,7 +265,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
// then // then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class, result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"[403] Subject ", " is not allowed to update partner uuid"); "[403] Subject ", " is not allowed to update hs_office_partner uuid");
} }
private void assertThatPartnerActuallyInDatabase(final HsOfficePartnerEntity saved) { private void assertThatPartnerActuallyInDatabase(final HsOfficePartnerEntity saved) {
@ -333,7 +333,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
// then // then
result.assertExceptionWithRootCauseMessage( result.assertExceptionWithRootCauseMessage(
JpaSystemException.class, JpaSystemException.class,
"[403] Subject ", " not allowed to delete partner"); "[403] Subject ", " not allowed to delete hs_office_partner");
assertThat(jpaAttempt.transacted(() -> { assertThat(jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
return partnerRepo.findByUuid(givenPartner.getUuid()); return partnerRepo.findByUuid(givenPartner.getUuid());

View File

@ -4,6 +4,8 @@ import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach;
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;
@ -11,6 +13,8 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -19,7 +23,7 @@ import static org.hamcrest.Matchers.*;
@SpringBootTest( @SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = HsadminNgApplication.class classes = { HsadminNgApplication.class, JpaAttempt.class }
) )
@Transactional @Transactional
class TestCustomerControllerAcceptanceTest { class TestCustomerControllerAcceptanceTest {
@ -32,9 +36,15 @@ class TestCustomerControllerAcceptanceTest {
@Autowired @Autowired
Context contextMock; Context contextMock;
@Autowired @Autowired
TestCustomerRepository testCustomerRepository; TestCustomerRepository testCustomerRepository;
@Autowired
JpaAttempt jpaAttempt;
Set<UUID> tempPartnerUuids = new HashSet<>();
@Nested @Nested
class ListCustomers { class ListCustomers {
@ -46,7 +56,7 @@ class TestCustomerControllerAcceptanceTest {
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/test/customers") .get("http://localhost/api/test/customers")
.then().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("[0].prefix", is("xxx")) .body("[0].prefix", is("xxx"))
@ -119,8 +129,8 @@ class TestCustomerControllerAcceptanceTest {
.body(""" .body("""
{ {
"reference": 90020, "reference": 90020,
"prefix": "ttt", "prefix": "uuu",
"adminUserName": "customer-admin@ttt.example.com" "adminUserName": "customer-admin@uuu.example.com"
} }
""") """)
.port(port) .port(port)
@ -129,22 +139,22 @@ class TestCustomerControllerAcceptanceTest {
.then().assertThat() .then().assertThat()
.statusCode(201) .statusCode(201)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("prefix", is("ttt")) .body("prefix", is("uuu"))
.header("Location", startsWith("http://localhost")) .header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on .extract().header("Location"); // @formatter:on
// finally, the new customer can be viewed by its own admin // finally, the new customer can be viewed by its own admin
final var newUserUuid = UUID.fromString( final var newUserUuid = toCleanup(UUID.fromString(
location.substring(location.lastIndexOf('/') + 1)); location.substring(location.lastIndexOf('/') + 1)));
context.define("customer-admin@ttt.example.com"); context.define("customer-admin@uuu.example.com");
assertThat(testCustomerRepository.findByUuid(newUserUuid)) assertThat(testCustomerRepository.findByUuid(newUserUuid))
.hasValueSatisfying(c -> assertThat(c.getPrefix()).isEqualTo("ttt")); .hasValueSatisfying(c -> assertThat(c.getPrefix()).isEqualTo("uuu"));
} }
@Test @Test
void globalAdmin_withoutAssumedRole_canAddCustomerWithGivenUuid() { void globalAdmin_withoutAssumedRole_canAddCustomerWithGivenUuid() {
final var givenUuid = UUID.randomUUID(); final var givenUuid = toCleanup(UUID.randomUUID());
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given() .given()
@ -238,4 +248,22 @@ class TestCustomerControllerAcceptanceTest {
assertThat(testCustomerRepository.findCustomerByOptionalPrefixLike("uuu")).hasSize(0); assertThat(testCustomerRepository.findCustomerByOptionalPrefixLike("uuu")).hasSize(0);
} }
} }
private UUID toCleanup(final UUID tempPartnerUuid) {
tempPartnerUuids.add(tempPartnerUuid);
return tempPartnerUuid;
}
@AfterEach
void cleanup() {
tempPartnerUuids.forEach(uuid -> {
jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net", null);
System.out.println("DELETING temporary partner: " + uuid);
final var entity = testCustomerRepository.findByUuid(uuid);
final var count = testCustomerRepository.deleteByUuid(uuid);
System.out.println("DELETED temporary partner: " + uuid + (count > 0 ? " successful" : " failed") + " (" + entity.map(TestCustomerEntity::getPrefix).orElse("???") + ")");
}).assertSuccessful();
});
}
} }

View File

@ -86,7 +86,7 @@ class TestPackageControllerAcceptanceTest {
void withDescriptionUpdatesDescription() { void withDescriptionUpdatesDescription() {
assumeThat(getDescriptionOfPackage("xxx00")) assumeThat(getDescriptionOfPackage("xxx00"))
.isEqualTo("Here can add your own description of package xxx00."); .isEqualTo("Here you can add your own description of package xxx00.");
final var randomDescription = RandomStringUtils.randomAlphanumeric(80); final var randomDescription = RandomStringUtils.randomAlphanumeric(80);
@ -104,7 +104,7 @@ class TestPackageControllerAcceptanceTest {
.port(port) .port(port)
.when() .when()
.patch("http://localhost/api/test/packages/{uuidOfPackage}", getUuidOfPackage("xxx00")) .patch("http://localhost/api/test/packages/{uuidOfPackage}", getUuidOfPackage("xxx00"))
.then() .then().log().all()
.assertThat() .assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
@ -118,7 +118,7 @@ class TestPackageControllerAcceptanceTest {
void withNullDescriptionUpdatesDescriptionToNull() { void withNullDescriptionUpdatesDescriptionToNull() {
assumeThat(getDescriptionOfPackage("xxx01")) assumeThat(getDescriptionOfPackage("xxx01"))
.isEqualTo("Here can add your own description of package xxx01."); .isEqualTo("Here you can add your own description of package xxx01.");
// @formatter:off // @formatter:off
RestAssured RestAssured
@ -147,7 +147,7 @@ class TestPackageControllerAcceptanceTest {
void withoutDescriptionDoesNothing() { void withoutDescriptionDoesNothing() {
assumeThat(getDescriptionOfPackage("xxx02")) assumeThat(getDescriptionOfPackage("xxx02"))
.isEqualTo("Here can add your own description of package xxx02."); .isEqualTo("Here you can add your own description of package xxx02.");
// @formatter:off // @formatter:off
RestAssured RestAssured
@ -163,7 +163,7 @@ class TestPackageControllerAcceptanceTest {
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("name", is("xxx02")) .body("name", is("xxx02"))
.body("description", is("Here can add your own description of package xxx02.")); // unchanged .body("description", is("Here you can add your own description of package xxx02.")); // unchanged
// @formatter:on // @formatter:on
} }
} }

View File

@ -98,22 +98,22 @@ class TestPackageRepositoryIntegrationTest {
// when // when
final var result1 = jpaAttempt.transacted(() -> { final var result1 = jpaAttempt.transacted(() -> {
globalAdminWithAssumedRole("test_package#xxx00.admin"); globalAdminWithAssumedRole("test_package#xxx00.owner");
pac.setDescription("description set by thread 1"); pac.setDescription("description set by thread 1");
testPackageRepository.save(pac); testPackageRepository.save(pac);
}); });
final var result2 = jpaAttempt.transacted(() -> { final var result2 = jpaAttempt.transacted(() -> {
globalAdminWithAssumedRole("test_package#xxx00.admin"); globalAdminWithAssumedRole("test_package#xxx00.owner");
pac.setDescription("description set by thread 2"); pac.setDescription("description set by thread 2");
testPackageRepository.save(pac); testPackageRepository.save(pac);
sleep(1500); sleep(1500);
}); });
// then // then
em.refresh(pac);
assertThat(pac.getDescription()).isEqualTo("description set by thread 1");
assertThat(result1.caughtException()).isNull(); assertThat(result1.caughtException()).isNull();
assertThat(result2.caughtException()).isInstanceOf(ObjectOptimisticLockingFailureException.class); assertThat(result2.caughtException()).isInstanceOf(ObjectOptimisticLockingFailureException.class);
em.refresh(pac);
assertThat(pac.getDescription()).isEqualTo("description set by thread 1");
} }
private void sleep(final int millis) { private void sleep(final int millis) {