diff --git a/src/main/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerEntity.java index 6f770f3f..84a87517 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerEntity.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.admin.partner; import lombok.*; import net.hostsharing.hsadminng.hs.admin.contact.HsAdminContactEntity; import net.hostsharing.hsadminng.hs.admin.person.HsAdminPersonEntity; +import org.hibernate.annotations.Cascade; import javax.persistence.*; import java.time.LocalDate; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepository.java index de76c10c..a7a27085 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepository.java @@ -27,4 +27,5 @@ public interface HsAdminPartnerRepository extends Repository 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; -- the owner role with full access for the creator assigned to the current user - ownerRole = createRole( + ownerRole := createRole( hsAdminContactOwner(NEW), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), beneathRole(globalAdmin()), @@ -73,11 +74,18 @@ begin grantedByRole(globalAdmin()) ); + -- the tenant role for those related users who can view the data + adminRole := createRole( + hsAdminContactAdmin(NEW), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), + beneathRole(ownerRole) + ); + -- the tenant role for those related users who can view the data perform createRole( hsAdminContactTenant(NEW), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(ownerRole) + beneathRole(adminRole) ); return NEW; @@ -221,7 +229,7 @@ create or replace function deleteHsAdminContact() returns trigger language plpgsql as $$ begin - if true or hasGlobalRoleGranted(currentUserUuid()) or + if hasGlobalRoleGranted(currentUserUuid()) or old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', 'hs_admin_contact', currentSubjectsUuids())) then delete from hs_admin_contact c where c.uuid = old.uuid; return old; diff --git a/src/main/resources/db/changelog/208-hs-admin-contact-test-data.sql b/src/main/resources/db/changelog/208-hs-admin-contact-test-data.sql index 6be893ab..209e209b 100644 --- a/src/main/resources/db/changelog/208-hs-admin-contact-test-data.sql +++ b/src/main/resources/db/changelog/208-hs-admin-contact-test-data.sql @@ -15,10 +15,12 @@ declare emailAddr varchar; begin currentTask = 'creating RBAC test contact ' || contLabel; - call defineContext(currentTask, null, 'alex@hostsharing.net', 'global#global.admin'); execute format('set local hsadminng.currentTask to %L', currentTask); emailAddr = 'customer-admin@' || cleanIdentifier(contLabel) || '.example.com'; + call defineContext(currentTask); + perform createRbacUser(emailAddr); + call defineContext(currentTask, null, emailAddr); raise notice 'creating test contact: %', contLabel; insert @@ -58,6 +60,7 @@ do language plpgsql $$ call createHsAdminContactTestData('first contact'); call createHsAdminContactTestData('second contact'); call createHsAdminContactTestData('third contact'); + call createHsAdminContactTestData('forth contact'); end; $$; --// diff --git a/src/main/resources/db/changelog/213-hs-admin-person-rbac.sql b/src/main/resources/db/changelog/213-hs-admin-person-rbac.sql index 906263e0..cbe637e8 100644 --- a/src/main/resources/db/changelog/213-hs-admin-person-rbac.sql +++ b/src/main/resources/db/changelog/213-hs-admin-person-rbac.sql @@ -58,13 +58,14 @@ create or replace function createRbacRolesForHsAdminPerson() strict as $$ declare ownerRole uuid; + adminRole uuid; begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; -- the owner role with full access for the creator assigned to the current user - ownerRole = createRole( + ownerRole := createRole( hsAdminPersonOwner(NEW), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), beneathRole(globalAdmin()), @@ -73,11 +74,18 @@ begin grantedByRole(globalAdmin()) ); + -- the tenant role for those related users who can view the data + adminRole := createRole( + hsAdminPersonAdmin(NEW), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), + beneathRole(ownerRole) + ); + -- the tenant role for those related users who can view the data perform createRole( hsAdminPersonTenant(NEW), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(ownerRole) + beneathRole(adminRole) ); return NEW; @@ -221,7 +229,7 @@ create or replace function deleteHsAdminPerson() returns trigger language plpgsql as $$ begin - if true or hasGlobalRoleGranted(currentUserUuid()) or + if hasGlobalRoleGranted(currentUserUuid()) or old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', 'hs_admin_person', currentSubjectsUuids())) then delete from hs_admin_person c where c.uuid = old.uuid; return old; diff --git a/src/main/resources/db/changelog/218-hs-admin-person-test-data.sql b/src/main/resources/db/changelog/218-hs-admin-person-test-data.sql index a5da8456..102c05f2 100644 --- a/src/main/resources/db/changelog/218-hs-admin-person-test-data.sql +++ b/src/main/resources/db/changelog/218-hs-admin-person-test-data.sql @@ -9,10 +9,10 @@ Creates a single person test record. */ create or replace procedure createHsAdminPersonTestData( - personType HsAdminPersonType, - tradeName varchar, - familyName varchar = null, - givenName varchar = null + newPersonType HsAdminPersonType, + newTradeName varchar, + newFamilyName varchar = null, + newGivenName varchar = null ) language plpgsql as $$ declare @@ -20,7 +20,7 @@ declare currentTask varchar; emailAddr varchar; begin - fullName := concat_ws(', ', personType, tradename, familyName, givenName); + fullName := concat_ws(', ', newTradeName, newFamilyName, newGivenName); currentTask = 'creating RBAC test person ' || fullName; emailAddr = 'person-' || left(cleanIdentifier(fullName), 32) || '@example.com'; call defineContext(currentTask); @@ -31,7 +31,7 @@ begin raise notice 'creating test person: %', fullName; insert into hs_admin_person (persontype, tradename, givenname, familyname) - values (personType, tradeName, givenName, familyName); + values (newPersonType, newTradeName, newGivenName, newFamilyName); end; $$; --// @@ -59,7 +59,7 @@ end; $$; do language plpgsql $$ begin - call createHsAdminPersonTestData('LEGAL', 'first person'); + call createHsAdminPersonTestData('LEGAL', 'First Impressions GmbH'); call createHsAdminPersonTestData('NATURAL', null, 'Peter', 'Smith'); call createHsAdminPersonTestData('LEGAL', 'Rockshop e.K.', 'Sandra', 'Miller'); call createHsAdminPersonTestData('SOLE_REPRESENTATION', 'Ostfriesische Kuhhandel OHG'); diff --git a/src/main/resources/db/changelog/223-hs-admin-partner-rbac.sql b/src/main/resources/db/changelog/223-hs-admin-partner-rbac.sql index aa04663b..c813815b 100644 --- a/src/main/resources/db/changelog/223-hs-admin-partner-rbac.sql +++ b/src/main/resources/db/changelog/223-hs-admin-partner-rbac.sql @@ -52,18 +52,23 @@ end; $$; Creates the roles and their assignments for a new partner for the AFTER INSERT TRIGGER. */ -create or replace function createRbacRolesForHsAdminContact() +create or replace function createRbacRolesForHsAdminPartner() returns trigger language plpgsql strict as $$ declare ownerRole uuid; adminRole uuid; + person hs_admin_person; + contact hs_admin_contact; begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; + select * from hs_admin_person as p where p.uuid = NEW.personUuid into person; + select * from hs_admin_contact as c where c.uuid = NEW.contactUuid into contact; + -- the owner role with full access for the global admins ownerRole = createRole( hsAdminPartnerOwner(NEW), @@ -75,14 +80,15 @@ begin adminRole = createRole( hsAdminPartnerAdmin(NEW), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), - beneathRole(globalAdmin()) + beneathRole(ownerRole) ); -- the tenant role for those related users who can view the data perform createRole( hsAdminPartnerTenant(NEW), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(ownerRole) + beneathRoles(array[hsAdminPartnerAdmin(NEW), hsAdminPersonAdmin(person), hsAdminContactAdmin(contact)]), + withSubRoles(array[hsAdminPersonTenant(person), hsAdminContactTenant(contact)]) ); return NEW; @@ -92,11 +98,11 @@ end; $$; An AFTER INSERT TRIGGER which creates the role structure for a new customer. */ -create trigger createRbacRolesForHsAdminContact_Trigger +create trigger createRbacRolesForHsAdminPartner_Trigger after insert on hs_admin_partner for each row -execute procedure createRbacRolesForHsAdminContact(); +execute procedure createRbacRolesForHsAdminPartner(); --// @@ -107,13 +113,14 @@ execute procedure createRbacRolesForHsAdminContact(); /* Deletes the roles and their assignments of a deleted partner for the BEFORE DELETE TRIGGER. */ -create or replace function deleteRbacRulesForHsAdminContact() +create or replace function deleteRbacRulesForHsAdminPartner() returns trigger language plpgsql strict as $$ begin if TG_OP = 'DELETE' then call deleteRole(findRoleId(hsAdminPartnerOwner(OLD))); + call deleteRole(findRoleId(hsAdminPartnerAdmin(OLD))); call deleteRole(findRoleId(hsAdminPartnerTenant(OLD))); else raise exception 'invalid usage of TRIGGER BEFORE DELETE'; @@ -124,11 +131,11 @@ end; $$; /* An BEFORE DELETE TRIGGER which deletes the role structure of a partner. */ -create trigger deleteRbacRulesForTestContact_Trigger +create trigger deleteRbacRulesForTestPartner_Trigger before delete on hs_admin_partner for each row -execute procedure deleteRbacRulesForHsAdminContact(); +execute procedure deleteRbacRulesForHsAdminPartner(); --// -- ============================================================================ @@ -142,9 +149,9 @@ execute procedure deleteRbacRulesForHsAdminContact(); create or replace view hs_admin_partner_iv as select target.uuid, cleanIdentifier( - (select idName from hs_admin_person_iv person where person.uuid = target.personuuid) + (select idName from hs_admin_person_iv p where p.uuid = target.personuuid) || '-' || - (select idName from hs_admin_contact_iv contact where contact.uuid = target.contactuuid) + (select idName from hs_admin_contact_iv c where c.uuid = target.contactuuid) ) as idName from hs_admin_partner as target; @@ -197,7 +204,7 @@ grant all privileges on hs_admin_partner_rv to restricted; /** Instead of insert trigger function for hs_admin_partner_rv. */ -create or replace function insertHsAdminContact() +create or replace function insertHsAdminPartner() returns trigger language plpgsql as $$ declare @@ -214,11 +221,11 @@ $$; /* Creates an instead of insert trigger for the hs_admin_partner_rv view. */ -create trigger insertHsAdminContact_Trigger +create trigger insertHsAdminPartner_Trigger instead of insert on hs_admin_partner_rv for each row -execute function insertHsAdminContact(); +execute function insertHsAdminPartner(); --// -- ============================================================================ @@ -228,11 +235,11 @@ execute function insertHsAdminContact(); /** Instead of delete trigger function for hs_admin_partner_rv. */ -create or replace function deleteHsAdminContact() +create or replace function deleteHsAdminPartner() returns trigger language plpgsql as $$ begin - if true or hasGlobalRoleGranted(currentUserUuid()) or + if hasGlobalRoleGranted(currentUserUuid()) or old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', 'hs_admin_partner', currentSubjectsUuids())) then delete from hs_admin_partner c where c.uuid = old.uuid; return old; @@ -243,11 +250,11 @@ end; $$; /* Creates an instead of delete trigger for the hs_admin_partner_rv view. */ -create trigger deleteHsAdminContact_Trigger +create trigger deleteHsAdminPartner_Trigger instead of delete on hs_admin_partner_rv for each row -execute function deleteHsAdminContact(); +execute function deleteHsAdminPartner(); --/ -- ============================================================================ @@ -274,7 +281,7 @@ $$; /** Used by the trigger to prevent the add-customer to current user respectively assumed roles. */ -create or replace function addHsAdminContactNotAllowedForCurrentSubjects() +create or replace function addHsAdminPartnerNotAllowedForCurrentSubjects() returns trigger language PLPGSQL as $$ @@ -292,6 +299,6 @@ create trigger hs_admin_partner_insert_trigger for each row -- TODO.spec: who is allowed to create new partners when ( not hasAssumedRole() ) -execute procedure addHsAdminContactNotAllowedForCurrentSubjects(); +execute procedure addHsAdminPartnerNotAllowedForCurrentSubjects(); --// diff --git a/src/main/resources/db/changelog/228-hs-admin-partner-test-data.sql b/src/main/resources/db/changelog/228-hs-admin-partner-test-data.sql index 218a1bec..81c85862 100644 --- a/src/main/resources/db/changelog/228-hs-admin-partner-test-data.sql +++ b/src/main/resources/db/changelog/228-hs-admin-partner-test-data.sql @@ -13,23 +13,23 @@ create or replace procedure createHsAdminPartnerTestData( personTradeName varcha declare currentTask varchar; idName varchar; - person hs_admin_person; - contact hs_admin_contact; + relatedPerson hs_admin_person; + relatedContact hs_admin_contact; begin idName := cleanIdentifier( personTradeName|| '-' || contactLabel); currentTask := 'creating RBAC test partner ' || idName; call defineContext(currentTask, null, 'alex@hostsharing.net', 'global#global.admin'); execute format('set local hsadminng.currentTask to %L', currentTask); - select p.* from hs_admin_person p where p.tradeName = personTradeName into person; - select c.* from hs_admin_contact c where c.label = contactLabel into contact; + select p.* from hs_admin_person p where p.tradeName = personTradeName into relatedPerson; + select c.* from hs_admin_contact c where c.label = contactLabel into relatedContact; raise notice 'creating test partner: %', idName; - raise notice '- using person (%): %', person.uuid, person; - raise notice '- using contact (%): %', contact.uuid, contact; + raise notice '- using person (%): %', relatedPerson.uuid, relatedPerson; + raise notice '- using contact (%): %', relatedContact.uuid, relatedContact; insert into hs_admin_partner (uuid, personuuid, contactuuid) - values (uuid_generate_v4(), person.uuid, contact.uuid); + values (uuid_generate_v4(), relatedPerson.uuid, relatedContact.uuid); end; $$; --// @@ -63,7 +63,7 @@ end; $$; do language plpgsql $$ begin - -- call createHsAdminPartnerTestData('first person', 'first contact'); + call createHsAdminPartnerTestData('First Impressions GmbH', 'first contact'); call createHsAdminPartnerTestData('Rockshop e.K.', 'second contact'); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java index 32851b0c..3c221bb3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java @@ -2,113 +2,272 @@ package net.hostsharing.hsadminng.hs.admin.partner; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.ContextBasedTest; +import net.hostsharing.hsadminng.hs.admin.contact.HsAdminContactRepository; import net.hostsharing.hsadminng.hs.admin.person.HsAdminPersonEntity; -import org.junit.jupiter.api.Disabled; +import net.hostsharing.hsadminng.hs.admin.person.HsAdminPersonRepository; +import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantRepository; +import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository; +import net.hostsharing.test.JpaAttempt; 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.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.orm.jpa.JpaSystemException; import org.springframework.test.annotation.DirtiesContext; import javax.persistence.EntityManager; import javax.servlet.http.HttpServletRequest; import java.util.List; +import java.util.UUID; -import static net.hostsharing.hsadminng.hs.admin.partner.TestHsAdminPartner.testLtd; +import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantDisplayExtractor.grantDisplaysOf; +import static net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleNameExtractor.roleNamesOf; import static net.hostsharing.test.JpaAttempt.attempt; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; -@Disabled @DataJpaTest -@ComponentScan(basePackageClasses = { Context.class, HsAdminPartnerRepository.class }) +@ComponentScan(basePackageClasses = { HsAdminPartnerRepository.class, Context.class, JpaAttempt.class }) @DirtiesContext class HsAdminPartnerRepositoryIntegrationTest extends ContextBasedTest { @Autowired - HsAdminPartnerRepository partnerRepository; + HsAdminPartnerRepository partnerRepo; + + @Autowired + HsAdminPersonRepository personRepo; + + @Autowired + HsAdminContactRepository contactRepo; + + @Autowired + RbacRoleRepository roleRepo; + + @Autowired + RbacGrantRepository grantRepo; @Autowired EntityManager em; + @Autowired + JpaAttempt jpaAttempt; + @MockBean HttpServletRequest request; @Nested - class CreateCustomer { + class CreatePartner { @Test - public void testHostsharingAdmin_withoutAssumedRole_canCreateNewCustomer() { + public void testHostsharingAdmin_withoutAssumedRole_canCreateNewPartner() { // given - context("alex@hostsharing.net", null); - final var count = partnerRepository.count(); + context("alex@hostsharing.net"); + final var count = partnerRepo.count(); + final var givenPerson = personRepo.findPersonByOptionalNameLike("First Impressions GmbH").get(0); + final var givenContact = contactRepo.findContactByOptionalLabelLike("first contact").get(0); // when - final var result = attempt(em, () -> { - return partnerRepository.save(testLtd); + final var newPartner = HsAdminPartnerEntity.builder() + .uuid(UUID.randomUUID()) + .person(givenPerson) + .contact(givenContact) + .build(); + return partnerRepo.save(newPartner); }); // then - assertThat(result.wasSuccessful()).isTrue(); + result.assertSuccessful(); assertThat(result.returnedValue()).isNotNull().extracting(HsAdminPartnerEntity::getUuid).isNotNull(); assertThatPartnerIsPersisted(result.returnedValue()); - assertThat(partnerRepository.count()).isEqualTo(count + 1); + assertThat(partnerRepo.count()).isEqualTo(count + 1); + } + + @Test + public void createsAndGrantsRoles() { + // given + context("alex@hostsharing.net"); + final var initialRoleCount = roleRepo.findAll().size(); + final var initialGrantCount = grantRepo.findAll().size(); + + // when + attempt(em, () -> { + final var givenPerson = personRepo.findPersonByOptionalNameLike("Erbengemeinschaft Bessler").get(0); + final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0); + final var newPartner = HsAdminPartnerEntity.builder() + .uuid(UUID.randomUUID()) + .person(givenPerson) + .contact(givenContact) + .build(); + return partnerRepo.save(newPartner); + }); + + // then + final var roles = roleRepo.findAll(); + assertThat(roleNamesOf(roles)).containsAll(List.of( + "hs_admin_partner#ErbengemeinschaftBesslerMelBessler-forthcontact.admin", + "hs_admin_partner#ErbengemeinschaftBesslerMelBessler-forthcontact.owner", + "hs_admin_partner#ErbengemeinschaftBesslerMelBessler-forthcontact.tenant")); + assertThat(roles.size()).as("invalid number of roles created") + .isEqualTo(initialRoleCount + 3); + + context("customer-admin@forthcontact.example.com"); + assertThat(grantDisplaysOf(grantRepo.findAll())).containsAll(List.of( + "{ grant assumed role hs_admin_contact#forthcontact.owner to user customer-admin@forthcontact.example.com by role global#global.admin }")); + + context("person-ErbengemeinschaftBesslerMelBessl@example.com"); + assertThat(grantDisplaysOf(grantRepo.findAll())).containsAll(List.of( + "{ grant assumed role hs_admin_person#ErbengemeinschaftBesslerMelBessler.owner to user person-ErbengemeinschaftBesslerMelBessl@example.com by role global#global.admin }")); } private void assertThatPartnerIsPersisted(final HsAdminPartnerEntity saved) { - final var found = partnerRepository.findByUuid(saved.getUuid()); + final var found = partnerRepo.findByUuid(saved.getUuid()); assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved); } } @Nested - class FindAllCustomers { + class FindAllPartners { @Test - public void globalAdmin_withoutAssumedRole_canViewAllCustomers() { + public void globalAdmin_withoutAssumedRole_canViewAllPartners() { // given - context("alex@hostsharing.net", null); + context("alex@hostsharing.net"); // when - final var result = partnerRepository.findPartnerByOptionalNameLike(null); + final var result = partnerRepo.findPartnerByOptionalNameLike(null); // then - allThesePartnersAreReturned(result, "Ixx AG", "Ypsilon GmbH", "Zett OHG"); + allThesePartnersAreReturned(result, "First Impressions GmbH", "Ostfriesische Kuhhandel OHG", "Rockshop e.K."); } + @Test + public void normalUser_canViewOnlyRelatedPartners() { + // given: + context("person-FirstImpressionsGmbH@example.com"); + + // when: + final var result = partnerRepo.findPartnerByOptionalNameLike(null); + + // then: + exactlyThesePartnersAreReturned(result, "First Impressions GmbH"); + } } @Nested - class FindByPrefixLike { + class FindByNameLike { @Test - public void globalAdmin_withoutAssumedRole_canViewAllCustomers() { + public void globalAdmin_withoutAssumedRole_canViewAllPartners() { // given - context("alex@hostsharing.net", null); + context("alex@hostsharing.net"); // when - final var result = partnerRepository.findPartnerByOptionalNameLike("Yps"); + final var result = partnerRepo.findPartnerByOptionalNameLike("Ostfriesische"); // then - exactlyTheseCustomersAreReturned(result, "Ypsilon GmbH"); - } - - @Test - public void customerAdmin_withoutAssumedRole_canViewOnlyItsOwnCustomer() { - // given: - context("customer-admin@xxx.example.com", null); - - // when: - final var result = partnerRepository.findPartnerByOptionalNameLike("Yps"); - - // then: - exactlyTheseCustomersAreReturned(result); + exactlyThesePartnersAreReturned(result, "Ostfriesische Kuhhandel OHG"); } } - void exactlyTheseCustomersAreReturned(final List actualResult, final String... partnerTradeNames) { + @Nested + class DeleteByUuid { + + @Test + public void globalAdmin_withoutAssumedRole_canDeleteAnyPartner() { + // given + context("alex@hostsharing.net", null); + final var givenPartner = givenSomeTemporaryPartnerBessler(); + + // when + final var result = jpaAttempt.transacted(() -> { + context("alex@hostsharing.net"); + partnerRepo.deleteByUuid(givenPartner.getUuid()); + }); + + // then + result.assertSuccessful(); + assertThat(jpaAttempt.transacted(() -> { + context("fran@hostsharing.net", null); + return partnerRepo.findByUuid(givenPartner.getUuid()); + }).assertSuccessful().returnedValue()).isEmpty(); + } + + @Test + public void nonGlobalAdmin_canNotDeleteTheirRelatedPartner() { + // given + context("alex@hostsharing.net", null); + final var givenPartner = givenSomeTemporaryPartnerBessler(); + + // when + final var result = jpaAttempt.transacted(() -> { + context("person-ErbengemeinschaftBesslerMelBessl@example.com"); + assertThat(partnerRepo.findByUuid(givenPartner.getUuid())).isPresent(); + + partnerRepo.deleteByUuid(givenPartner.getUuid()); + }); + + // then + result.assertExceptionWithRootCauseMessage(JpaSystemException.class, + "[403] User person-ErbengemeinschaftBesslerMelBessl@example.com not allowed to delete partner"); + assertThat(jpaAttempt.transacted(() -> { + context("alex@hostsharing.net"); + return partnerRepo.findByUuid(givenPartner.getUuid()); + }).assertSuccessful().returnedValue()).isPresent(); // still there + } + + @Test + public void deletingAPartnerAlsoDeletesRelatedRolesAndGrants() { + // given + context("alex@hostsharing.net"); + final var initialRoleCount = roleRepo.findAll().size(); + final var givenPartner = givenSomeTemporaryPartnerBessler(); + assumeThat(roleRepo.findAll().size()).as("unexpected number of roles created") + .isEqualTo(initialRoleCount + 3);; + + // when + final var result = jpaAttempt.transacted(() -> { + context("alex@hostsharing.net"); + partnerRepo.deleteByUuid(givenPartner.getUuid()); + }).assertSuccessful(); + + // then + final var roles = roleRepo.findAll(); + assertThat(roleNamesOf(roles)).doesNotContainAnyElementsOf(List.of( + "hs_admin_partner#ErbengemeinschaftBesslerMelBessler-forthcontact.admin", + "hs_admin_partner#ErbengemeinschaftBesslerMelBessler-forthcontact.owner", + "hs_admin_partner#ErbengemeinschaftBesslerMelBessler-forthcontact.tenant")); + assertThat(roles.size()).as("invalid number of roles created") + .isEqualTo(initialRoleCount); + + context("customer-admin@forthcontact.example.com"); + assertThat(grantDisplaysOf(grantRepo.findAll())).doesNotContain( + "{ grant assumed role hs_admin_contact#forthcontact.owner to user customer-admin@forthcontact.example.com by role global#global.admin }"); + + context("person-ErbengemeinschaftBesslerMelBessl@example.com"); + assertThat(grantDisplaysOf(grantRepo.findAll())).doesNotContain( + "{ grant assumed role hs_admin_person#ErbengemeinschaftBesslerMelBessler.owner to user person-ErbengemeinschaftBesslerMelBessl@example.com by role global#global.admin }"); + } + } + + private HsAdminPartnerEntity givenSomeTemporaryPartnerBessler() { + return jpaAttempt.transacted(() -> { + context("alex@hostsharing.net"); + final var givenPerson = personRepo.findPersonByOptionalNameLike("Erbengemeinschaft Bessler").get(0); + final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0); + final var newPartner = HsAdminPartnerEntity.builder() + .uuid(UUID.randomUUID()) + .person(givenPerson) + .contact(givenContact) + .build(); + + return partnerRepo.save(newPartner); + }).assertSuccessful().returnedValue(); + } + + void exactlyThesePartnersAreReturned(final List actualResult, final String... partnerTradeNames) { assertThat(actualResult) .hasSize(partnerTradeNames.length) .extracting(HsAdminPartnerEntity::getPerson) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/admin/person/HsAdminPersonRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/admin/person/HsAdminPersonRepositoryIntegrationTest.java index 345c180f..7333d8d6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/admin/person/HsAdminPersonRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/admin/person/HsAdminPersonRepositoryIntegrationTest.java @@ -225,6 +225,8 @@ class HsAdminPersonRepositoryIntegrationTest extends ContextBasedTest { }).assertSuccessful().returnedValue()).hasSize(0); } + // TODO.test: can NOT delete test is missing + @Test public void deletingAPersonAlsoDeletesRelatedRolesAndGrants() { // given diff --git a/tools/generate b/tools/generate index fe29debf..03844adb 100755 --- a/tools/generate +++ b/tools/generate @@ -2,14 +2,15 @@ mkdir -p src/test/java/net/hostsharing/hsadminng/hs/admin/partner -#sed -e 's/hs-admin-contact/hs-admin-partner/g' \ -# -e 's/hs_admin_contact/hs_admin_partner/g' \ -# -e 's/HsAdminContact/HsAdminPartner/g' \ -# -e 's/hsAdminContact/hsAdminPartner/g' \ -# -e 's/contact/partner/g' \ -#src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java +sed -e 's/hs-admin-contact/hs-admin-partner/g' \ + -e 's/hs_admin_contact/hs_admin_partner/g' \ + -e 's/HsAdminContact/HsAdminPartner/g' \ + -e 's/hsAdminContact/hsAdminPartner/g' \ + -e 's/contact/partner/g' \ +src/test/java/net/hostsharing/hsadminng/hs/admin/partner/HsAdminPartnerRepositoryIntegrationTest.java +exit sed -e 's/hs-admin-contact/hs-admin-partner/g' \ -e 's/hs_admin_contact/hs_admin_partner/g' \