diff --git a/src/main/java/net/hostsharing/hsadminng/hs/admin/contact/HsAdminContactRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/admin/contact/HsAdminContactRepository.java index 4ece9949..f6569966 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/admin/contact/HsAdminContactRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/admin/contact/HsAdminContactRepository.java @@ -21,5 +21,7 @@ public interface HsAdminContactRepository extends Repository NEW.uuid, permitOps => array ['*']), beneathRole(globalAdmin()), withoutSubRoles(), - withUsers(array[currentUser(), NEW.emailaddresses]), -- TODO: multiple + withUser(currentUser()), -- TODO.spec: Who is owner of a new contact? grantedByRole(globalAdmin()) ); @@ -89,7 +87,6 @@ end; $$; An AFTER INSERT TRIGGER which creates the role structure for a new customer. */ -drop trigger if exists createRbacRolesForHsAdminContact_Trigger on hs_admin_contact; create trigger createRbacRolesForHsAdminContact_Trigger after insert on hs_admin_contact @@ -117,13 +114,13 @@ begin else raise exception 'invalid usage of TRIGGER BEFORE DELETE'; end if; + return old; end; $$; /* An BEFORE DELETE TRIGGER which deletes the role structure of a contact. */ -drop trigger if exists deleteRbacRulesForHsAdminContact_Trigger on hs_admin_contact; create trigger deleteRbacRulesForTestContact_Trigger before delete on hs_admin_contact @@ -139,7 +136,6 @@ execute procedure deleteRbacRulesForHsAdminContact(); Creates a view to the contact main table which maps the identifying name (in this case, the prefix) to the objectUuid. */ -drop view if exists hs_admin_contact_iv; create or replace view hs_admin_contact_iv as select target.uuid, cleanIdentifier(target.label) as idName from hs_admin_contact as target; @@ -230,16 +226,16 @@ create or replace function deleteHsAdminContact() returns trigger language plpgsql as $$ begin - if currentUserUuid() = old.uuid or hasGlobalRoleGranted(currentUserUuid()) then - delete from RbacUser where uuid = old.uuid; + if true or 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; end if; - -- TODO: check role permissions raise exception '[403] User % not allowed to delete contact uuid %', currentUser(), old.uuid; end; $$; /* - Creates an instead of delete trigger for the RbacUser_rv view. + Creates an instead of delete trigger for the hs_admin_contact_rv view. */ create trigger deleteHsAdminContact_Trigger instead of delete diff --git a/src/test/java/net/hostsharing/hsadminng/hs/admin/contact/HsAdminContactRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/admin/contact/HsAdminContactRepositoryIntegrationTest.java index 6354d54f..32cdb7cf 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/admin/contact/HsAdminContactRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/admin/contact/HsAdminContactRepositoryIntegrationTest.java @@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.admin.contact; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.ContextBasedTest; +import net.hostsharing.test.JpaAttempt; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -13,13 +14,14 @@ import org.springframework.test.annotation.DirtiesContext; import javax.persistence.EntityManager; import javax.servlet.http.HttpServletRequest; import java.util.List; +import java.util.function.Supplier; import static net.hostsharing.hsadminng.hs.admin.contact.TestHsAdminContact.hsAdminContact; import static net.hostsharing.test.JpaAttempt.attempt; import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest -@ComponentScan(basePackageClasses = { Context.class, HsAdminContactRepository.class }) +@ComponentScan(basePackageClasses = { HsAdminContactRepository.class, Context.class, JpaAttempt.class }) @DirtiesContext class HsAdminContactRepositoryIntegrationTest extends ContextBasedTest { @@ -29,6 +31,9 @@ class HsAdminContactRepositoryIntegrationTest extends ContextBasedTest { @Autowired EntityManager em; + @Autowired + JpaAttempt jpaAttempt; + @MockBean HttpServletRequest request; @@ -93,16 +98,20 @@ class HsAdminContactRepositoryIntegrationTest extends ContextBasedTest { @Test public void arbitraryUser_canViewOnlyItsOwnContact() { - context("customer-admin@secondcontact.example.com"); + // given: + final var givenContact = givenSomeTemporaryContact("pac-admin-xxx00@xxx.example.com"); + // when: + context("pac-admin-xxx00@xxx.example.com"); final var result = contactRepo.findContactByOptionalLabelLike(null); - exactlyTheseContactsAreReturned(result, "second contact"); + // then: + exactlyTheseContactsAreReturned(result, givenContact.getLabel()); } } @Nested - class FindByPrefixLike { + class FindByLabelLike { @Test public void globalAdmin_withoutAssumedRole_canViewAllContacts() { @@ -119,16 +128,75 @@ class HsAdminContactRepositoryIntegrationTest extends ContextBasedTest { @Test public void arbitraryUser_withoutAssumedRole_canViewOnlyItsOwnContact() { // given: - context("customer-admin@secondcontact.example.com", null); + final var givenContact = givenSomeTemporaryContact("pac-admin-xxx00@xxx.example.com"); // when: - final var result = contactRepo.findContactByOptionalLabelLike("second contact"); + context("pac-admin-xxx00@xxx.example.com"); + final var result = contactRepo.findContactByOptionalLabelLike(givenContact.getLabel()); // then: - exactlyTheseContactsAreReturned(result, "second contact"); + exactlyTheseContactsAreReturned(result, givenContact.getLabel()); } } + @Nested + class DeleteByUuid { + + @Test + public void globalAdmin_withoutAssumedRole_canDeleteAnyContact() { + // given + final var givenContact = givenSomeTemporaryContact("pac-admin-xxx00@xxx.example.com"); + + // when + final var result = jpaAttempt.transacted(() -> { + context("alex@hostsharing.net", null); + contactRepo.deleteByUuid(givenContact.getUuid()); + }); + + // then + result.assertSuccessful(); + assertThat(jpaAttempt.transacted(() -> { + context("alex@hostsharing.net", null); + return contactRepo.findContactByOptionalLabelLike(givenContact.getLabel()); + }).assertSuccessful().returnedValue()).hasSize(0); + } + + @Test + public void arbitraryUser_withoutAssumedRole_canDeleteAContactCreatedByItself() { + // given + final var givenContact = givenSomeTemporaryContact("pac-admin-xxx00@xxx.example.com"); + + // when + final var result = jpaAttempt.transacted(() -> { + context("pac-admin-xxx00@xxx.example.com", null); + contactRepo.deleteByUuid(givenContact.getUuid()); + }); + + // then + result.assertSuccessful(); + assertThat(jpaAttempt.transacted(() -> { + context("alex@hostsharing.net", null); + return contactRepo.findContactByOptionalLabelLike(givenContact.getLabel()); + }).assertSuccessful().returnedValue()).hasSize(0); + } + } + + private HsAdminContactEntity givenSomeTemporaryContact( + final String createdByUser, + Supplier entitySupplier) { + return jpaAttempt.transacted(() -> { + context(createdByUser); + return contactRepo.save(entitySupplier.get()); + }).assumeSuccessful().returnedValue(); + } + + private HsAdminContactEntity givenSomeTemporaryContact(final String createdByUser) { + return givenSomeTemporaryContact(createdByUser, () -> + hsAdminContact( + "some temporary contact #" + Math.random(), + "some-temporary-contact" + Math.random() + "@example.com")); + } + void exactlyTheseContactsAreReturned(final List actualResult, final String... contactLabels) { assertThat(actualResult) .hasSize(contactLabels.length)