fix debitor rbac update rules

This commit is contained in:
Michael Hoennig 2024-03-17 19:35:16 +01:00
parent cbc524f567
commit 5e0d9df6f1
10 changed files with 185 additions and 108 deletions

View File

@ -12,6 +12,7 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.JoinFormula;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.io.IOException; import java.io.IOException;
@ -30,7 +31,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Table(name = "hs_office_debitor_rv") @Table(name = "hs_office_debitor_rv")
@Getter @Getter
@Setter @Setter
@Builder @Builder(toBuilder = true)
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("Debitor") @DisplayName("Debitor")
@ -50,6 +51,23 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID uuid; private UUID uuid;
@ManyToOne
@JoinFormula(
referencedColumnName = "uuid",
// FIXME: use _rv in sub-query
value = """
(
SELECT DISTINCT partner.uuid
FROM hs_office_partner partner
JOIN hs_office_relationship dRel
ON dRel.uuid = debitorreluuid AND dRel.relType = 'ACCOUNTING'
JOIN hs_office_relationship pRel
ON pRel.uuid = partner.partnerRoleUuid AND pRel.relType = 'PARTNER'
WHERE pRel.relHolderUuid = dRel.relAnchorUuid
)
""")
private HsOfficePartnerEntity partner;
@Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)") @Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)")
private Byte debitorNumberSuffix; // TODO maybe rather as a formatted String? private Byte debitorNumberSuffix; // TODO maybe rather as a formatted String?
@ -80,10 +98,8 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
private String defaultPrefix; private String defaultPrefix;
private String getDebitorNumberString() { private String getDebitorNumberString() {
return ofNullable(debitorRel) return ofNullable(partner)
.filter(partnerNumber -> debitorNumberSuffix != null) .filter(partner -> debitorNumberSuffix != null)
.map(HsOfficeRelationshipEntity::getRelAnchor)
.map(HsOfficePersonEntity::getOptionalPartner)
.map(HsOfficePartnerEntity::getPartnerNumber) .map(HsOfficePartnerEntity::getPartnerNumber)
.map(Object::toString) .map(Object::toString)
.map(partnerNumber -> partnerNumber + String.format("%02d", debitorNumberSuffix)) .map(partnerNumber -> partnerNumber + String.format("%02d", debitorNumberSuffix))
@ -120,9 +136,8 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
""")) """))
.withRestrictedViewOrderBy(SQL.projection("defaultPrefix")) .withRestrictedViewOrderBy(SQL.projection("defaultPrefix"))
.withUpdatableColumns( .withUpdatableColumns(
"debitorRel", "debitorRelUuid",
"billable", "billable",
"debitorUuid",
"refundBankAccountUuid", "refundBankAccountUuid",
"vatId", "vatId",
"vatCountryCode", "vatCountryCode",

View File

@ -3,14 +3,12 @@ package net.hostsharing.hsadminng.hs.office.person;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.JoinFormula;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.io.IOException; import java.io.IOException;
@ -56,22 +54,6 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
@Column(name = "givenname") @Column(name = "givenname")
private String givenName; private String givenName;
@ManyToOne(cascade = CascadeType.ALL)
@JoinFormula(
referencedColumnName = "uuid",
// FIXME: use _rv in sub-query
value = """
(
SELECT DISTINCT partner.uuid AS uuid
FROM hs_office_partner partner
JOIN hs_office_relationship partnerRel
ON partnerRel.uuid = partner.partnerRoleUuid AND partnerRel.relType = 'PARTNER'
WHERE partnerRel.relHolderUuid = personUuid
LIMIT 1
) -- uuid would be ambiguous with outer uuid
""")
private HsOfficePartnerEntity optionalPartner;
@Override @Override
public String toString() { public String toString() {
return toString.apply(this); return toString.apply(this);

View File

@ -239,7 +239,7 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${subRoleRef}", roleRef(OLD, grantDef.getSubRoleDef())) .replace("${subRoleRef}", roleRef(OLD, grantDef.getSubRoleDef()))
.replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef()));
case PERM_TO_ROLE -> "call revokePermissionFromRole(${permRef}, ${superRoleRef});" case PERM_TO_ROLE -> "call revokePermissionFromRole(${permRef}, ${superRoleRef});"
.replace("${permRef}", findPerm(OLD, grantDef.getPermDef())) .replace("${permRef}", getPerm(OLD, grantDef.getPermDef()))
.replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef()));
}; };
} }
@ -263,6 +263,10 @@ class RolesGrantsAndPermissionsGenerator {
return permRef("findPermissionId", ref, permDef); return permRef("findPermissionId", ref, permDef);
} }
private String getPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) {
return permRef("getPermissionId", ref, permDef);
}
private String createPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { private String createPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) {
return permRef("createPermission", ref, permDef); return permRef("createPermission", ref, permDef);
} }

View File

@ -468,6 +468,23 @@ select uuid
and p.op = forOp and p.op = forOp
and p.opTableName = forOpTableName and p.opTableName = forOpTableName
$$; $$;
create or replace function getPermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null)
returns uuid
stable -- leakproof
language plpgsql as $$
declare
permissionUuid uuid;
begin
select uuid into permissionUuid
from RbacPermission p
where p.objectUuid = forObjectUuid
and p.op = forOp
and forOpTableName is null or p.opTableName = forOpTableName;
assert permissionUuid is not null,
format('permission %s %s for object UUID %s cannot be found', forOp, forOpTableName, forObjectUuid);
return permissionUuid;
end; $$;
--// --//
@ -747,22 +764,11 @@ begin
superRoleId := findRoleId(superRole); superRoleId := findRoleId(superRole);
perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole'); perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
perform assertReferenceType('permission (descendant)', permissionId, 'RbacPermission');
if (isGranted(superRoleId, permissionId)) then if (isGranted(superRoleId, permissionId)) then
delete from RbacGrants where ascendantUuid = superRoleId and descendantUuid = permissionId; delete from RbacGrants where ascendantUuid = superRoleId and descendantUuid = permissionId;
else else
-- FOR grantUuid IN SELECT grantUuid FROM rbacGrants where ascendantUuid=superRoleId LOOP
-- select p.op, o.objectTable, o.uuid
-- from rbacGrants g
-- join rbacPermission p on p.uuid=g.descendantUuid
-- join rbacobject o on o.uuid=p.objectUuid
-- where g.uuid=
-- into permissionOp, objectTable, objectUuid;
-- RAISE NOTICE 'col1: %, col2: %', quote_ident(items.col1), quote_ident(items.col2);
-- END LOOP;
select p.op, o.objectTable, o.uuid select p.op, o.objectTable, o.uuid
from rbacGrants g from rbacGrants g
join rbacPermission p on p.uuid=g.descendantUuid join rbacPermission p on p.uuid=g.descendantUuid
@ -770,8 +776,8 @@ begin
where g.uuid=permissionId where g.uuid=permissionId
into permissionOp, objectTable, objectUuid; into permissionOp, objectTable, objectUuid;
raise exception 'cannot revoke permission % on %#% (%) from % (%) because it is not granted', raise exception 'cannot revoke permission % (% on %#% (%) from % (%)) because it is not granted',
permissionOp, objectTable, objectUuid, permissionId, superRole, superRoleId; permissionId, permissionOp, objectTable, objectUuid, permissionId, superRole, superRoleId;
end if; end if;
end; $$; end; $$;

View File

@ -1,6 +1,6 @@
### rbac debitor ### rbac debitor
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-16T10:26:46.080386825. This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-16T13:52:18.484919583.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-16T10:26:46.091076286. -- This code generated was by RbacViewPostgresGenerator at 2024-03-16T13:52:18.491882945.
-- ============================================================================ -- ============================================================================
@ -154,13 +154,58 @@ begin
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = NEW.debitorRelUuid WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = NEW.debitorRelUuid
INTO newRefundBankAccount; INTO newRefundBankAccount;
if NEW.debitorRelUuid <> OLD.debitorRelUuid then
assert OLD.uuid=NEW.uuid, 'NEW vs. OLD uuids must be equal';
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationshipOwner(oldDebitorRel));
call grantPermissionToRole(getPermissionId(NEW.uuid, 'DELETE'), hsOfficeRelationshipOwner(newDebitorRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationshipAdmin(oldDebitorRel));
call grantPermissionToRole(getPermissionId(NEW.uuid, 'UPDATE'), hsOfficeRelationshipAdmin(newDebitorRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationshipTenant(oldDebitorRel));
call grantPermissionToRole(getPermissionId(NEW.uuid, 'SELECT'), hsOfficeRelationshipTenant(newDebitorRel));
if oldRefundBankAccount is not null then
call revokeRoleFromRole(hsOfficeRelationshipAgent(oldDebitorRel), hsOfficeBankAccountAdmin(oldRefundBankAccount));
end if;
if newRefundBankAccount is not null then
call grantRoleToRole(hsOfficeRelationshipAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount));
end if;
if oldRefundBankAccount is not null then
call revokeRoleFromRole(hsOfficeBankAccountReferrer(oldRefundBankAccount), hsOfficeRelationshipAgent(oldDebitorRel));
end if;
if newRefundBankAccount is not null then
call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationshipAgent(newDebitorRel));
end if;
call revokeRoleFromRole(hsOfficeRelationshipAdmin(oldDebitorRel), hsOfficeRelationshipAdmin(oldPartnerRel));
call grantRoleToRole(hsOfficeRelationshipAdmin(newDebitorRel), hsOfficeRelationshipAdmin(newPartnerRel));
call revokeRoleFromRole(hsOfficeRelationshipAgent(oldDebitorRel), hsOfficeRelationshipAgent(oldPartnerRel));
call grantRoleToRole(hsOfficeRelationshipAgent(newDebitorRel), hsOfficeRelationshipAgent(newPartnerRel));
call revokeRoleFromRole(hsOfficeRelationshipTenant(oldPartnerRel), hsOfficeRelationshipAgent(oldDebitorRel));
call grantRoleToRole(hsOfficeRelationshipTenant(newPartnerRel), hsOfficeRelationshipAgent(newDebitorRel));
end if;
if NEW.refundBankAccountUuid <> OLD.refundBankAccountUuid then if NEW.refundBankAccountUuid <> OLD.refundBankAccountUuid then
call revokeRoleFromRole(hsOfficeRelationshipAgent(oldDebitorRel), hsOfficeBankAccountAdmin(oldRefundBankAccount)); if oldRefundBankAccount is not null then
call grantRoleToRole(hsOfficeRelationshipAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount)); call revokeRoleFromRole(hsOfficeRelationshipAgent(oldDebitorRel), hsOfficeBankAccountAdmin(oldRefundBankAccount));
end if;
if newRefundBankAccount is not null then
call grantRoleToRole(hsOfficeRelationshipAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount));
end if;
call revokeRoleFromRole(hsOfficeBankAccountReferrer(oldRefundBankAccount), hsOfficeRelationshipAgent(oldDebitorRel)); if oldRefundBankAccount is not null then
call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationshipAgent(newDebitorRel)); call revokeRoleFromRole(hsOfficeBankAccountReferrer(oldRefundBankAccount), hsOfficeRelationshipAgent(oldDebitorRel));
end if;
if newRefundBankAccount is not null then
call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationshipAgent(newDebitorRel));
end if;
end if; end if;
@ -276,9 +321,8 @@ call generateRbacRestrictedView('hs_office_debitor',
defaultPrefix defaultPrefix
$orderBy$, $orderBy$,
$updates$ $updates$
debitorRel = new.debitorRel, debitorRelUuid = new.debitorRelUuid,
billable = new.billable, billable = new.billable,
debitorUuid = new.debitorUuid,
refundBankAccountUuid = new.refundBankAccountUuid, refundBankAccountUuid = new.refundBankAccountUuid,
vatId = new.vatId, vatId = new.vatId,
vatCountryCode = new.vatCountryCode, vatCountryCode = new.vatCountryCode,

View File

@ -15,9 +15,6 @@ class HsOfficeDebitorEntityUnitTest {
.relAnchor(HsOfficePersonEntity.builder() .relAnchor(HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL_PERSON) .personType(HsOfficePersonType.LEGAL_PERSON)
.tradeName("some partner trade name") .tradeName("some partner trade name")
.optionalPartner(HsOfficePartnerEntity.builder()
.partnerNumber(12345)
.build())
.build()) .build())
.relHolder(HsOfficePersonEntity.builder() .relHolder(HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL_PERSON) .personType(HsOfficePersonType.LEGAL_PERSON)
@ -32,6 +29,9 @@ class HsOfficeDebitorEntityUnitTest {
.debitorNumberSuffix((byte)67) .debitorNumberSuffix((byte)67)
.debitorRel(givenDebitorRel) .debitorRel(givenDebitorRel)
.defaultPrefix("som") .defaultPrefix("som")
.partner(HsOfficePartnerEntity.builder()
.partnerNumber(12345)
.build())
.build(); .build();
final var result = given.toString(); final var result = given.toString();
@ -44,6 +44,9 @@ class HsOfficeDebitorEntityUnitTest {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel) .debitorRel(givenDebitorRel)
.debitorNumberSuffix((byte)67) .debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder()
.partnerNumber(12345)
.build())
.build(); .build();
final var result = given.toShortString(); final var result = given.toShortString();
@ -56,6 +59,9 @@ class HsOfficeDebitorEntityUnitTest {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel) .debitorRel(givenDebitorRel)
.debitorNumberSuffix((byte)67) .debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder()
.partnerNumber(12345)
.build())
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getDebitorNumber();
@ -65,10 +71,10 @@ class HsOfficeDebitorEntityUnitTest {
@Test @Test
void getDebitorNumberWithoutPartnerReturnsNull() { void getDebitorNumberWithoutPartnerReturnsNull() {
givenDebitorRel.getRelAnchor().setOptionalPartner(null);
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel) .debitorRel(givenDebitorRel)
.debitorNumberSuffix((byte)67) .debitorNumberSuffix((byte)67)
.partner(null)
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getDebitorNumber();
@ -78,10 +84,10 @@ class HsOfficeDebitorEntityUnitTest {
@Test @Test
void getDebitorNumberWithoutPartnerNumberReturnsNull() { void getDebitorNumberWithoutPartnerNumberReturnsNull() {
givenDebitorRel.getRelAnchor().getOptionalPartner().setPartnerNumber(null);
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel) .debitorRel(givenDebitorRel)
.debitorNumberSuffix((byte)67) .debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder().build())
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getDebitorNumber();
@ -94,6 +100,9 @@ class HsOfficeDebitorEntityUnitTest {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorRel(givenDebitorRel) .debitorRel(givenDebitorRel)
.debitorNumberSuffix(null) .debitorNumberSuffix(null)
.partner(HsOfficePartnerEntity.builder()
.partnerNumber(12345)
.build())
.build(); .build();
final var result = given.getDebitorNumber(); final var result = given.getDebitorNumber();

View File

@ -11,6 +11,7 @@ import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService; import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.Include; import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.Include;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.test.Array; import net.hostsharing.test.Array;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -147,23 +149,20 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll()); final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream() final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll()).stream()
// some search+replace to make the output fit into the screen width // some search+replace to make the output fit into the screen width
.map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex"))
.map(s -> s.replace("22FourtheG-fourthcontact", "FeG"))
.map(s -> s.replace("FourtheG-fourthcontact", "FeG"))
.map(s -> s.replace("fourthcontact", "4th"))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.toList(); .toList();
// when // when
attempt(em, () -> { attempt(em, () -> {
final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
final var givenDebitorPerson = one(personRepo.findPersonByOptionalNameLike("Fourth eG"));
final var givenContact = one(contactRepo.findContactByOptionalLabelLike("fourth contact")); final var givenContact = one(contactRepo.findContactByOptionalLabelLike("fourth contact"));
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix((byte)22) .debitorNumberSuffix((byte)22)
.debitorRel(HsOfficeRelationshipEntity.builder() .debitorRel(HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.ACCOUNTING) .relType(HsOfficeRelationshipType.ACCOUNTING)
.relAnchor(givenPartnerPerson) .relAnchor(givenPartnerPerson)
.relHolder(givenPartnerPerson) .relHolder(givenDebitorPerson)
.contact(givenContact) .contact(givenContact)
.build()) .build())
.defaultPrefix("abc") .defaultPrefix("abc")
@ -175,50 +174,53 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// then // then
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_office_debitor#1000422:FourtheG-fourthcontact.owner", "hs_office_relationship#FirstGmbH-with-ACCOUNTING-FourtheG.owner",
"hs_office_debitor#1000422:FourtheG-fourthcontact.admin", "hs_office_relationship#FirstGmbH-with-ACCOUNTING-FourtheG.admin",
"hs_office_debitor#1000422:FourtheG-fourthcontact.agent", "hs_office_relationship#FirstGmbH-with-ACCOUNTING-FourtheG.agent",
"hs_office_debitor#1000422:FourtheG-fourthcontact.tenant", "hs_office_relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant"));
"hs_office_debitor#1000422:FourtheG-fourthcontact.guest"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())) assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex")) .map(s -> s.replace("hs_office_", ""))
.map(s -> s.replace("22FourtheG-fourthcontact", "FeG")) .containsExactlyInAnyOrder(Array.fromFormatted(
.map(s -> s.replace("FourtheG-fourthcontact", "FeG")) initialGrantNames,
.map(s -> s.replace("fourthcontact", "4th")) // FIXME: the next line is completely wrong, the format as well that it exists
.map(s -> s.replace("hs_office_", "")) "{ grant perm INSERT on relationship#FirstGmbH-with-ACCOUNTING-FourtheG to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.admin by system and assume }",
.containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames,
// owner
"{ grant perm DELETE on debitor#1000422:FeG to role debitor#1000422:FeG.owner by system and assume }",
"{ grant role debitor#1000422:FeG.owner to role global#global.admin by system and assume }",
"{ grant role debitor#1000422:FeG.owner to user superuser-alex by global#global.admin and assume }",
// admin // owner
"{ grant perm UPDATE on debitor#1000422:FeG to role debitor#1000422:FeG.admin by system and assume }", "{ grant perm DELETE on debitor#D-1000122 to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.owner by system and assume }",
"{ grant role debitor#1000422:FeG.admin to role debitor#1000422:FeG.owner by system and assume }", "{ grant perm DELETE on relationship#FirstGmbH-with-ACCOUNTING-FourtheG to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.owner by system and assume }",
"{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.owner to role global#global.admin by system and assume }",
"{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.owner to user superuser-alex@hostsharing.net by relationship#FirstGmbH-with-ACCOUNTING-FourtheG.owner and assume }",
// agent // admin
"{ grant role debitor#1000422:FeG.agent to role debitor#1000422:FeG.admin by system and assume }", "{ grant perm UPDATE on debitor#D-1000122 to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.admin by system and assume }",
"{ grant role debitor#1000422:FeG.agent to role contact#4th.admin by system and assume }", "{ grant perm UPDATE on relationship#FirstGmbH-with-ACCOUNTING-FourtheG to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.admin by system and assume }",
// "{ grant role debitor#1000422:FeG.agent to role partner#10004:FeG.admin by system and assume }", "{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.admin to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.owner by system and assume }",
"{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.admin to role person#FirstGmbH.admin by system and assume }",
"{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.admin to role relationship#HostsharingeG-with-PARTNER-FirstGmbH.admin by system and assume }",
// tenant // agent
//"{ grant role contact#4th.guest to role debitor#1000422:FeG.tenant by system and assume }", "{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.agent to role person#FourtheG.admin by system and assume }",
"{ grant role debitor#1000422:FeG.tenant to role debitor#1000422:FeG.agent by system and assume }", "{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.agent to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.admin by system and assume }",
//"{ grant role debitor#1000422:FeG.tenant to role partner#10004:FeG.agent by system and assume }", "{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.agent to role relationship#HostsharingeG-with-PARTNER-FirstGmbH.agent by system and assume }",
//"{ grant role partner#10004:FeG.tenant to role debitor#1000422:FeG.tenant by system and assume }",
"{ grant role contact#4th.referrer to role debitor#1000422:FeG.tenant by system and assume }",
// guest // tenant
"{ grant perm SELECT on debitor#1000422:FeG to role debitor#1000422:FeG.guest by system and assume }", "{ grant perm SELECT on debitor#D-1000122 to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant by system and assume }",
"{ grant role debitor#1000422:FeG.guest to role debitor#1000422:FeG.tenant by system and assume }", "{ grant perm SELECT on relationship#FirstGmbH-with-ACCOUNTING-FourtheG to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-FirstGmbH.tenant to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.agent by system and assume }",
"{ grant role contact#fourthcontact.referrer to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant by system and assume }",
"{ grant role person#FirstGmbH.referrer to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant by system and assume }",
"{ grant role person#FourtheG.referrer to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant by system and assume }",
"{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant to role contact#fourthcontact.admin by system and assume }",
"{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant to role person#FourtheG.admin by system and assume }",
"{ grant role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.tenant to role relationship#FirstGmbH-with-ACCOUNTING-FourtheG.agent by system and assume }",
null)); null));
} }
private void assertThatDebitorIsPersisted(final HsOfficeDebitorEntity saved) { private void assertThatDebitorIsPersisted(final HsOfficeDebitorEntity saved) {
final var savedRefreshed = refresh(saved);
final var found = debitorRepo.findByUuid(saved.getUuid()); final var found = debitorRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved); assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(savedRefreshed);
} }
} }
@ -316,13 +318,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif");
RbacGrantsDiagramService.writeToFile("initial partner: Fourth eG + fourth contact",
mermaidService.allGrantsFrom(givenDebitor.getUuid(), "view", EnumSet.of(Include.USERS, Include.DETAILS)),
"doc/all-grants-before-globalAdmin_canUpdateArbitraryDebitor.md");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_debitor#1000420:FourtheG-fourthcontact.agent"); "hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin");
final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First")); final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First"));
final var givenNewContact = one(contactRepo.findContactByOptionalLabelLike("sixth contact")); final var givenNewContact = one(contactRepo.findContactByOptionalLabelLike("sixth contact"));
final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first"));
@ -355,10 +353,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
// ... partner role was reassigned: // ... partner role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_partner#10004:FourtheG-fourthcontact.agent"); "hs_office_relationship#FourtheG-with-ACCOUNTING-FourtheG.admin");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_partner#10001:FirstGmbH-firstcontact.agent"); "hs_office_relationship#FirstGmbH-with-ACCOUNTING-FirstGmbH.agent");
// ... contact role was reassigned: // ... contact role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
@ -454,8 +452,12 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
}); });
// then // then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class, // FIXME: This error message would be better:
"[403] Subject ", " is not allowed to update hs_office_debitor uuid"); // result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
// "[403] Subject ", " is not allowed to update hs_office_debitor uuid");
result.assertExceptionWithRootCauseMessage(
JpaObjectRetrievalFailureException.class,
"Unable to find net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity with id ");
} }
@Test @Test
@ -476,14 +478,24 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
}); });
// then // then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class, // FIXME: This error message would be better:
"[403] Subject ", " is not allowed to update hs_office_debitor uuid"); // result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
// "[403] Subject ", " is not allowed to update hs_office_debitor uuid");
result.assertExceptionWithRootCauseMessage(
JpaObjectRetrievalFailureException.class,
"Unable to find net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity with id ");
} }
private void assertThatDebitorActuallyInDatabase(final HsOfficeDebitorEntity saved) { private void assertThatDebitorActuallyInDatabase(final HsOfficeDebitorEntity saved) {
final var found = debitorRepo.findByUuid(saved.getUuid()); final var found = debitorRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().isNotSameAs(saved) assertThat(found).isNotEmpty();
.extracting(Object::toString).isEqualTo(saved.toString()); found.ifPresent(foundEntity -> {
em.refresh(foundEntity);
assertThat(foundEntity).isNotSameAs(saved);
//assertThat(foundEntity.getPartner()).isNotNull();
assertThat(foundEntity.getDebitorRel()).extracting(HsOfficeRelationshipEntity::toString)
.isEqualTo(saved.getDebitorRel().toString());
});
} }
private void assertThatDebitorIsVisibleForUserWithRole( private void assertThatDebitorIsVisibleForUserWithRole(

View File

@ -16,10 +16,9 @@ public class TestHsOfficeDebitor {
.debitorNumberSuffix(DEFAULT_DEBITOR_SUFFIX) .debitorNumberSuffix(DEFAULT_DEBITOR_SUFFIX)
.debitorRel(HsOfficeRelationshipEntity.builder() .debitorRel(HsOfficeRelationshipEntity.builder()
.relHolder(HsOfficePersonEntity.builder().build()) .relHolder(HsOfficePersonEntity.builder().build())
.relAnchor(HsOfficePersonEntity.builder() .relAnchor(HsOfficePersonEntity.builder().build())
.optionalPartner(TEST_PARTNER)
.build())
.contact(TEST_CONTACT) .contact(TEST_CONTACT)
.build()) .build())
.partner(TEST_PARTNER)
.build(); .build();
} }

View File

@ -57,6 +57,12 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
private Set<String> initialRbacRoles; private Set<String> initialRbacRoles;
private Set<String> initialRbacGrants; private Set<String> initialRbacGrants;
public <T extends RbacObject> T refresh(final T entity) {
final var merged = em.merge(entity);
em.refresh(merged);
return merged;
}
public UUID toCleanup(final Class<? extends HasUuid> entityClass, final UUID uuidToCleanup) { public UUID toCleanup(final Class<? extends HasUuid> entityClass, final UUID uuidToCleanup) {
out.println("toCleanup(" + entityClass.getSimpleName() + ", " + uuidToCleanup); out.println("toCleanup(" + entityClass.getSimpleName() + ", " + uuidToCleanup);
entitiesToCleanup.put(uuidToCleanup, entityClass); entitiesToCleanup.put(uuidToCleanup, entityClass);