diff --git a/doc/hs-office-mailinglist-subscriptions.md b/doc/hs-office-mailinglist-subscriptions.md new file mode 100644 index 00000000..d33d4f33 --- /dev/null +++ b/doc/hs-office-mailinglist-subscriptions.md @@ -0,0 +1,14 @@ +# Sketch UI for mailinglist subscriptions + +```PlantUML +@startsalt +{{^==Mailinglist-Subscriptions +Members-Announce: | [X] | ^frank.meiler@example.org^ | [Create New] +Members-Discussion: | [X] | ^frank.meiler@example.org^ | [Create New] +Customers-Announce: | [X] | ^frank.meiler@example.org^ | [Create New] +Operations-Announce: | [X] | ^Default Contact Data^ | [Create New] +Operations-Discussion: | [ ] | ^ ^ | [Create New] + +}} +@endsalt +``` diff --git a/src/main/resources/db/changelog/230-hs-office-partner.sql b/src/main/resources/db/changelog/230-hs-office-partner.sql index 8595c1f1..c623d1bf 100644 --- a/src/main/resources/db/changelog/230-hs-office-partner.sql +++ b/src/main/resources/db/changelog/230-hs-office-partner.sql @@ -33,7 +33,7 @@ create table hs_office_partner ( uuid uuid unique references RbacObject (uuid) initially deferred, partnerNumber numeric(5), - partnerRoleUuid uuid not null references hs_office_relationship(uuid), + partnerRoleUuid uuid not null references hs_office_relationship(uuid) on delete cascade, personUuid uuid not null references hs_office_person(uuid), -- TODO: remove, replaced by partnerRoleUuid contactUuid uuid not null references hs_office_contact(uuid), -- TODO: remove, replaced by partnerRoleUuid detailsUuid uuid not null references hs_office_partner_details(uuid) on delete cascade diff --git a/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql b/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql index 7882b93a..ee3a4882 100644 --- a/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql +++ b/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql @@ -27,12 +27,17 @@ create or replace function hsOfficePartnerRbacRolesTrigger() language plpgsql strict as $$ declare + oldPartnerRole hs_office_relationship; + newPartnerRole hs_office_relationship; + oldPerson hs_office_person; newPerson hs_office_person; + oldContact hs_office_contact; newContact hs_office_contact; begin + select * from hs_office_relationship as r where r.uuid = NEW.partnerroleuuid into newPartnerRole; select * from hs_office_person as p where p.uuid = NEW.personUuid into newPerson; select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact; @@ -52,6 +57,7 @@ begin incomingSuperRoles => array[ hsOfficePartnerOwner(NEW)], outgoingSubRoles => array[ + hsOfficeRelationshipTenant(newPartnerRole), hsOfficePersonTenant(newPerson), hsOfficeContactTenant(newContact)] ); @@ -60,6 +66,7 @@ begin hsOfficePartnerAgent(NEW), incomingSuperRoles => array[ hsOfficePartnerAdmin(NEW), + hsOfficeRelationshipAdmin(newPartnerRole), hsOfficePersonAdmin(newPerson), hsOfficeContactAdmin(newContact)] ); @@ -69,6 +76,7 @@ begin incomingSuperRoles => array[ hsOfficePartnerAgent(NEW)], outgoingSubRoles => array[ + hsOfficeRelationshipTenant(newPartnerRole), hsOfficePersonGuest(newPerson), hsOfficeContactGuest(newContact)] ); @@ -109,6 +117,19 @@ begin elsif TG_OP = 'UPDATE' then + if OLD.partnerRoleUuid <> NEW.partnerRoleUuid then + select * from hs_office_relationship as r where r.uuid = OLD.partnerRoleUuid into oldPartnerRole; + + call revokeRoleFromRole(hsOfficeRelationshipTenant(oldPerson), hsOfficePartnerAdmin(OLD)); + call grantRoleToRole(hsOfficeRelationshipTenant(newPerson), hsOfficePartnerAdmin(NEW)); + + call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficeRelationshipAdmin(oldPerson)); + call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficeRelationshipAdmin(newPerson)); + + call revokeRoleFromRole(hsOfficeRelationshipGuest(oldPerson), hsOfficePartnerTenant(OLD)); + call grantRoleToRole(hsOfficeRelationshipGuest(newPerson), hsOfficePartnerTenant(NEW)); + end if; + if OLD.personUuid <> NEW.personUuid then select * from hs_office_person as p where p.uuid = OLD.personUuid into oldPerson; @@ -179,7 +200,7 @@ call generateRbacIdentityView('hs_office_partner', $idName$ call generateRbacRestrictedView('hs_office_partner', '(select idName from hs_office_person_iv p where p.uuid = target.personUuid)', $updates$ - partherRoleUuid = new.partnerRoleUuid, + partnerRoleUuid = new.partnerRoleUuid, personUuid = new.personUuid, contactUuid = new.contactUuid $updates$); @@ -191,7 +212,7 @@ call generateRbacRestrictedView('hs_office_partner', --changeset hs-office-partner-rbac-NEW-PARTNER:1 endDelimiter:--// -- ---------------------------------------------------------------------------- /* - Creates a global permission for new-partner and assigns it to the hostsharing admins role. + Creates a global permission for new-partner and assigns it to the Hostsharing admins role. */ do language plpgsql $$ declare diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java index 0e0f45ca..4ac6ff1a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java @@ -763,7 +763,8 @@ public class ImportOfficeData extends ContextBasedTest { if (containsRole(rec, "vip-contact")) { addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.VIP_CONTACT); } - verifyContainsOnly(rec.getString("roles"), "partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"); + verifyContainsOnly(rec.getString("roles"), + "partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"); }); optionallyAddMissingContractualRelationships(); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java index 6126112e..c5f8558a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java @@ -122,7 +122,7 @@ class HsOfficePartnerControllerAcceptanceTest { context.define("superuser-alex@hostsharing.net"); final var givenPerson = personRepo.findPersonByOptionalNameLike("Third").get(0); - final var givenContact = contactRepo.findContactByOptionalLabelLike("forth").get(0); + final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); final var location = RestAssured // @formatter:off .given() @@ -191,8 +191,9 @@ class HsOfficePartnerControllerAcceptanceTest { void globalAdmin_canNotAddPartner_ifPersonDoesNotExist() { context.define("superuser-alex@hostsharing.net"); + final var mandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0); final var givenPersonUuid = UUID.fromString("3fa85f64-5717-4562-b3fc-2c963f66afa6"); - final var givenContact = contactRepo.findContactByOptionalLabelLike("forth").get(0); + final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); final var location = RestAssured // @formatter:off .given() @@ -201,11 +202,22 @@ class HsOfficePartnerControllerAcceptanceTest { .body(""" { "partnerNumber": "12345", - "contactUuid": "%s", + "partnerRole": { + "relAnchorUuid": "%s", + "relType": "PARTNER", + "relHolder": "%s", + "contact": "%s", + }, "personUuid": "%s", + "contactUuid": "%s", "details": {} } - """.formatted(givenContact.getUuid(), givenPersonUuid)) + """.formatted( + mandantPerson.getUuid(), + givenPersonUuid, + givenContact.getUuid(), + givenPersonUuid, + givenContact.getUuid())) .port(port) .when() .post("http://localhost/api/hs/office/partners") diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index 0c4741f9..4ac8abb0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -74,13 +74,23 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { // given context("superuser-alex@hostsharing.net"); final var count = partnerRepo.count(); - final var givenPerson = personRepo.findPersonByOptionalNameLike("First GmbH").get(0); + final var givenMandantorPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0); + final var givenPartnerPerson = personRepo.findPersonByOptionalNameLike("First GmbH").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("first contact").get(0); + final var partnerRole = HsOfficeRelationshipEntity.builder() + .relHolder(givenPartnerPerson) + .relType(HsOfficeRelationshipType.PARTNER) + .relAnchor(givenMandantorPerson) + .contact(givenContact) + .build(); + relationshipRepo.save(partnerRole); + // when final var result = attempt(em, () -> { final var newPartner = toCleanup(HsOfficePartnerEntity.builder() - .person(givenPerson) + .partnerRole(partnerRole) + .person(givenPartnerPerson) .contact(givenContact) .details(HsOfficePartnerDetailsEntity.builder() .build()) @@ -148,11 +158,12 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { .containsExactlyInAnyOrder(Array.fromFormatted( initialGrantNames, // relationship - TODO: check and cleanup + "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role partner#22222:EBess-4th.admin by system and assume }", + "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role partner#22222:EBess-4th.tenant by system and assume }", + "{ grant role partner#22222:EBess-4th.agent to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }", "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to role global#global.admin by system and assume }", "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role contact#4th.admin by system and assume }", - "{ grant role person#HostsharingeG.tenant to role person#EBess.admin by system and assume }", "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role person#EBess.admin by system and assume }", - "{ grant role person#EBess.tenant to role person#HostsharingeG.admin by system and assume }", "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to role person#HostsharingeG.admin by system and assume }", "{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role person#HostsharingeG.admin by system and assume }", "{ grant perm edit on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }", @@ -311,6 +322,10 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { // given context("superuser-alex@hostsharing.net"); final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "ninth"); + final var newPartnerRole = em.createNativeQuery( + "select uuid from hs_office_relationship where uuid=:partnerRoleUuid") + .setParameter("partnerRoleUuid", givenPartner.getPartnerRole().getUuid()) + .getSingleResult(); assertThatPartnerIsVisibleForUserWithRole( givenPartner, "hs_office_partner#22222:ErbenBesslerMelBessler-ninthcontact.agent"); @@ -412,7 +427,8 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - return partnerRepo.deleteByUuid(givenPartner.getUuid()); + return relationshipRepo.deleteByUuid(givenPartner.getPartnerRole().getUuid()); +// return partnerRepo.deleteByUuid(givenPartner.getUuid()); }); // then @@ -455,11 +471,23 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { final Integer partnerNumber, final String person, final String contact) { return jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - final var givenPerson = personRepo.findPersonByOptionalNameLike(person).get(0); + final var givenMandantorPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0); + final var givenPartnerPerson = personRepo.findPersonByOptionalNameLike(person).get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0); + + final var partnerRole = HsOfficeRelationshipEntity.builder() + .relHolder(givenPartnerPerson) + .relType(HsOfficeRelationshipType.PARTNER) + .relAnchor(givenMandantorPerson) + .contact(givenContact) + .build(); + relationshipRepo.save(partnerRole); + em.flush(); // TODO: why is that necessary? + final var newPartner = HsOfficePartnerEntity.builder() .partnerNumber(partnerNumber) - .person(givenPerson) + .partnerRole(partnerRole) + .person(givenPartnerPerson) .contact(givenContact) .details(HsOfficePartnerDetailsEntity.builder().build()) .build();