add-trigger-object-to-rbacgrant (#18)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #18
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-02-24 09:04:07 +01:00
parent c7003148ae
commit d9558f2cfe
17 changed files with 116 additions and 36 deletions

View File

@ -467,12 +467,13 @@ end $$;
create table RbacGrants create table RbacGrants
( (
uuid uuid primary key default uuid_generate_v4(), uuid uuid primary key default uuid_generate_v4(),
grantedByTriggerOf uuid references RbacObject (uuid) on delete cascade initially deferred ,
grantedByRoleUuid uuid references RbacRole (uuid), grantedByRoleUuid uuid references RbacRole (uuid),
ascendantUuid uuid references RbacReference (uuid), ascendantUuid uuid references RbacReference (uuid),
descendantUuid uuid references RbacReference (uuid), descendantUuid uuid references RbacReference (uuid),
assumed boolean not null default true, -- auto assumed (true) vs. needs assumeRoles (false) assumed boolean not null default true, -- auto assumed (true) vs. needs assumeRoles (false)
unique (ascendantUuid, descendantUuid) unique (ascendantUuid, descendantUuid),
); constraint rbacGrant_createdBy check ( grantedByRoleUuid is null or grantedByTriggerOf is null) );
create index on RbacGrants (ascendantUuid); create index on RbacGrants (ascendantUuid);
create index on RbacGrants (descendantUuid); create index on RbacGrants (descendantUuid);
@ -576,8 +577,8 @@ begin
perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission'); perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
insert insert
into RbacGrants (ascendantUuid, descendantUuid, assumed) into RbacGrants (grantedByTriggerOf, ascendantUuid, descendantUuid, assumed)
values (roleUuid, permissionIds[i], true) values (currentTriggerObjectUuid(), roleUuid, permissionIds[i], true)
on conflict do nothing; -- allow granting multiple times on conflict do nothing; -- allow granting multiple times
end loop; end loop;
end; end;
@ -594,8 +595,8 @@ begin
end if; end if;
insert insert
into RbacGrants (ascendantuuid, descendantUuid, assumed) into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed)
values (superRoleId, subRoleId, doAssume) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume)
on conflict do nothing; -- allow granting multiple times on conflict do nothing; -- allow granting multiple times
end; $$; end; $$;
@ -617,8 +618,8 @@ begin
end if; end if;
insert insert
into RbacGrants (ascendantuuid, descendantUuid, assumed) into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed)
values (superRoleId, subRoleId, doAssume) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume)
on conflict do nothing; -- allow granting multiple times on conflict do nothing; -- allow granting multiple times
end; $$; end; $$;
@ -640,8 +641,8 @@ begin
end if; end if;
insert insert
into RbacGrants (ascendantuuid, descendantUuid, assumed) into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed)
values (superRoleId, subRoleId, doAssume) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume)
on conflict do nothing; -- allow granting multiple times on conflict do nothing; -- allow granting multiple times
end; $$; end; $$;

View File

@ -56,6 +56,7 @@ drop view if exists rbacgrants_ev;
create or replace view rbacgrants_ev as create or replace view rbacgrants_ev as
-- @formatter:off -- @formatter:off
select x.grantUuid as uuid, select x.grantUuid as uuid,
x.grantedByTriggerOf as grantedByTriggerOf,
go.objectTable || '#' || findIdNameByObjectUuid(go.objectTable, go.uuid) || '.' || r.roletype as grantedByRoleIdName, go.objectTable || '#' || findIdNameByObjectUuid(go.objectTable, go.uuid) || '.' || r.roletype as grantedByRoleIdName,
x.ascendingIdName as ascendantIdName, x.ascendingIdName as ascendantIdName,
x.descendingIdName as descendantIdName, x.descendingIdName as descendantIdName,
@ -65,6 +66,7 @@ create or replace view rbacgrants_ev as
x.assumed x.assumed
from ( from (
select g.uuid as grantUuid, select g.uuid as grantUuid,
g.grantedbytriggerof as grantedbytriggerof,
g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid, g.assumed, g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid, g.assumed,
coalesce( coalesce(

View File

@ -0,0 +1,61 @@
--liquibase formatted sql
-- ============================================================================
--changeset rbac-trigger-context-ENTER:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure enterTriggerForObjectUuid(currentObjectUuid uuid)
language plpgsql as $$
declare
existingObjectUuid text;
begin
existingObjectUuid = current_setting('hsadminng.currentObjectUuid', true);
if (existingObjectUuid > '' ) then
raise exception '[500] currentObjectUuid already defined, already in trigger of "%"', existingObjectUuid;
end if;
execute format('set local hsadminng.currentObjectUuid to %L', currentObjectUuid);
end; $$;
-- ============================================================================
--changeset rbac-trigger-context-CURRENT-ID:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Returns the uuid of the object uuid whose trigger is currently executed as set via `enterTriggerForObjectUuid(...)`.
*/
create or replace function currentTriggerObjectUuid()
returns uuid
stable -- leakproof
language plpgsql as $$
declare
currentObjectUuid uuid;
begin
begin
currentObjectUuid = current_setting('hsadminng.currentObjectUuid')::uuid;
return currentObjectUuid;
exception
when others then
return null::uuid;
end;
end; $$;
--//
-- ============================================================================
--changeset rbac-trigger-context-LEAVE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure leaveTriggerForObjectUuid(currentObjectUuid uuid)
language plpgsql as $$
declare
existingObjectUuid uuid;
begin
existingObjectUuid = current_setting('hsadminng.currentObjectUuid', true);
if ( existingObjectUuid <> currentObjectUuid ) then
raise exception '[500] currentObjectUuid does not match: "%"', existingObjectUuid;
end if;
execute format('reset hsadminng.currentObjectUuid');
end; $$;

View File

@ -34,6 +34,8 @@ begin
raise exception 'invalid usage of TRIGGER AFTER INSERT'; raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if; end if;
call enterTriggerForObjectUuid(NEW.uuid);
-- the owner role with full access for Hostsharing administrators -- the owner role with full access for Hostsharing administrators
testCustomerOwnerUuid = createRoleWithGrants( testCustomerOwnerUuid = createRoleWithGrants(
testCustomerOwner(NEW), testCustomerOwner(NEW),
@ -59,6 +61,7 @@ begin
permissions => array['view'] permissions => array['view']
); );
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -26,13 +26,13 @@ create or replace function createRbacRolesForTestPackage()
strict as $$ strict as $$
declare declare
parentCustomer test_customer; parentCustomer test_customer;
packageOwnerRoleUuid uuid;
packageAdminRoleUuid uuid;
begin begin
if TG_OP <> 'INSERT' then if TG_OP <> 'INSERT' then
raise exception 'invalid usage of TRIGGER AFTER INSERT'; raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if; end if;
call enterTriggerForObjectUuid(NEW.uuid);
select * from test_customer as c where c.uuid = NEW.customerUuid into parentCustomer; select * from test_customer as c where c.uuid = NEW.customerUuid into parentCustomer;
-- 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
@ -57,6 +57,7 @@ begin
outgoingSubRoles => array[testCustomerTenant(parentCustomer)] outgoingSubRoles => array[testCustomerTenant(parentCustomer)]
); );
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -53,6 +53,8 @@ begin
raise exception 'invalid usage of TRIGGER AFTER INSERT'; raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if; end if;
call enterTriggerForObjectUuid(NEW.uuid);
select * from test_package where uuid = NEW.packageUuid into parentPackage; select * from test_package where uuid = NEW.packageUuid into parentPackage;
-- an owner role is created and assigned to the package's admin group -- an owner role is created and assigned to the package's admin group
@ -72,6 +74,7 @@ begin
-- a tenent role is only created on demand -- a tenent role is only created on demand
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -33,6 +33,7 @@ declare
oldContact hs_office_contact; oldContact hs_office_contact;
newContact hs_office_contact; newContact hs_office_contact;
begin begin
call enterTriggerForObjectUuid(NEW.uuid);
hsOfficeRelationshipTenant := hsOfficeRelationshipTenant(NEW); hsOfficeRelationshipTenant := hsOfficeRelationshipTenant(NEW);
@ -96,6 +97,7 @@ begin
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -36,6 +36,7 @@ declare
oldContact hs_office_contact; oldContact hs_office_contact;
newContact hs_office_contact; newContact hs_office_contact;
begin begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_relationship as r where r.uuid = NEW.partnerroleuuid into newPartnerRole; 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_person as p where p.uuid = NEW.personUuid into newPerson;
@ -159,6 +160,7 @@ begin
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -30,6 +30,7 @@ declare
newHsOfficeDebitor hs_office_debitor; newHsOfficeDebitor hs_office_debitor;
newHsOfficeBankAccount hs_office_bankAccount; newHsOfficeBankAccount hs_office_bankAccount;
begin begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_debitor as p where p.uuid = NEW.debitorUuid into newHsOfficeDebitor; select * from hs_office_debitor as p where p.uuid = NEW.debitorUuid into newHsOfficeDebitor;
select * from hs_office_bankAccount as c where c.uuid = NEW.bankAccountUuid into newHsOfficeBankAccount; select * from hs_office_bankAccount as c where c.uuid = NEW.bankAccountUuid into newHsOfficeBankAccount;
@ -75,6 +76,7 @@ begin
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -36,6 +36,7 @@ declare
newBankAccount hs_office_bankaccount; newBankAccount hs_office_bankaccount;
oldBankAccount hs_office_bankaccount; oldBankAccount hs_office_bankaccount;
begin begin
call enterTriggerForObjectUuid(NEW.uuid);
hsOfficeDebitorTenant := hsOfficeDebitorTenant(NEW); hsOfficeDebitorTenant := hsOfficeDebitorTenant(NEW);
@ -145,6 +146,7 @@ begin
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -30,6 +30,7 @@ declare
newHsOfficePartner hs_office_partner; newHsOfficePartner hs_office_partner;
newHsOfficeDebitor hs_office_debitor; newHsOfficeDebitor hs_office_debitor;
begin begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_partner as p where p.uuid = NEW.partnerUuid into newHsOfficePartner; select * from hs_office_partner as p where p.uuid = NEW.partnerUuid into newHsOfficePartner;
select * from hs_office_debitor as c where c.uuid = NEW.mainDebitorUuid into newHsOfficeDebitor; select * from hs_office_debitor as c where c.uuid = NEW.mainDebitorUuid into newHsOfficeDebitor;
@ -74,6 +75,7 @@ begin
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -29,6 +29,7 @@ create or replace function hsOfficeCoopSharesTransactionRbacRolesTrigger()
declare declare
newHsOfficeMembership hs_office_membership; newHsOfficeMembership hs_office_membership;
begin begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_membership as p where p.uuid = NEW.membershipUuid into newHsOfficeMembership; select * from hs_office_membership as p where p.uuid = NEW.membershipUuid into newHsOfficeMembership;
@ -49,6 +50,7 @@ begin
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -29,6 +29,7 @@ create or replace function hsOfficeCoopAssetsTransactionRbacRolesTrigger()
declare declare
newHsOfficeMembership hs_office_membership; newHsOfficeMembership hs_office_membership;
begin begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_membership as p where p.uuid = NEW.membershipUuid into newHsOfficeMembership; select * from hs_office_membership as p where p.uuid = NEW.membershipUuid into newHsOfficeMembership;
@ -49,6 +50,7 @@ begin
raise exception 'invalid usage of TRIGGER'; raise exception 'invalid usage of TRIGGER';
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW; return NEW;
end; $$; end; $$;

View File

@ -25,6 +25,8 @@ databaseChangeLog:
file: db/changelog/054-rbac-context.sql file: db/changelog/054-rbac-context.sql
- include: - include:
file: db/changelog/055-rbac-views.sql file: db/changelog/055-rbac-views.sql
- include:
file: db/changelog/056-rbac-trigger-context.sql
- include: - include:
file: db/changelog/057-rbac-role-builder.sql file: db/changelog/057-rbac-role-builder.sql
- include: - include:

View File

@ -278,19 +278,18 @@ public class ImportOfficeData extends ContextBasedTest {
2000002=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000002=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000003=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='null null, null'), 2000003=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='null null, null'),
2000004=rel(relAnchor='NP Mellies, Michael', relType='OPERATIONS', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000004=rel(relAnchor='NP Mellies, Michael', relType='OPERATIONS', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000005=rel(relAnchor='LP JM GmbH', relType='EX_PARTNER', relHolder='LP JM e.K.', contact='JM e.K.'), 2000005=rel(relAnchor='NP Mellies, Michael', relType='REPRESENTATIVE', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000006=rel(relAnchor='LP JM GmbH', relType='OPERATIONS', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000006=rel(relAnchor='LP JM GmbH', relType='EX_PARTNER', relHolder='LP JM e.K.', contact='JM e.K.'),
2000007=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000007=rel(relAnchor='LP JM GmbH', relType='OPERATIONS', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000008=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='operations-announce', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000008=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000009=rel(relAnchor='LP JM GmbH', relType='REPRESENTATIVE', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000009=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='operations-announce', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000010=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='members-announce', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000010=rel(relAnchor='LP JM GmbH', relType='REPRESENTATIVE', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000011=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='customers-announce', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000011=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='members-announce', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000012=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), 2000012=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='customers-announce', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000013=rel(relAnchor='?? Test PS', relType='OPERATIONS', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000013=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'),
2000014=rel(relAnchor='?? Test PS', relType='REPRESENTATIVE', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000014=rel(relAnchor='?? Test PS', relType='OPERATIONS', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000015=rel(relAnchor='NP Mellies, Michael', relType='SUBSCRIBER', relMark='operations-announce', relHolder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '), 2000015=rel(relAnchor='?? Test PS', relType='REPRESENTATIVE', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000016=rel(relAnchor='NP Mellies, Michael', relType='REPRESENTATIVE', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000016=rel(relAnchor='NP Mellies, Michael', relType='SUBSCRIBER', relMark='operations-announce', relHolder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga ')
2000017=rel(relAnchor='null null, null', relType='REPRESENTATIVE', relHolder='null null, null')
} }
"""); """);
} }
@ -412,7 +411,7 @@ public class ImportOfficeData extends ContextBasedTest {
idsToRemove.add(id); idsToRemove.add(id);
} }
}); });
assertThat(idsToRemove.size()).isEqualTo(2); // only from partner #99 (partner+contractual roles) assertThat(idsToRemove.size()).isEqualTo(1); // only from partner #99 (partner+contractual roles)
idsToRemove.forEach(id -> relationships.remove(id)); idsToRemove.forEach(id -> relationships.remove(id));
} }
@ -421,7 +420,7 @@ public class ImportOfficeData extends ContextBasedTest {
void removeEmptyPartners() { void removeEmptyPartners() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
// avoid a error when persisting the deliberetely invalid partner entry #99 // avoid a error when persisting the deliberately invalid partner entry #99
final var idsToRemove = new HashSet<Integer>(); final var idsToRemove = new HashSet<Integer>();
partners.forEach( (id, r) -> { partners.forEach( (id, r) -> {
// such a record // such a record
@ -439,7 +438,7 @@ public class ImportOfficeData extends ContextBasedTest {
void removeEmptyDebitors() { void removeEmptyDebitors() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
// avoid a error when persisting the deliberetely invalid partner entry #99 // avoid a error when persisting the deliberately invalid partner entry #99
final var idsToRemove = new HashSet<Integer>(); final var idsToRemove = new HashSet<Integer>();
debitors.forEach( (id, r) -> { debitors.forEach( (id, r) -> {
// such a record // such a record
@ -881,11 +880,9 @@ public class ImportOfficeData extends ContextBasedTest {
if (relationships.values().stream() if (relationships.values().stream()
.filter(rel -> rel.getRelAnchor() == partnerPerson && rel.getRelType() == HsOfficeRelationshipType.REPRESENTATIVE) .filter(rel -> rel.getRelAnchor() == partnerPerson && rel.getRelType() == HsOfficeRelationshipType.REPRESENTATIVE)
.findFirst().isEmpty()) { .findFirst().isEmpty()) {
//addRelationship(partnerPerson, partnerPerson, partner.getContact(), HsOfficeRelationshipType.REPRESENTATIVE);
contractualMissing.add(partner.getPartnerNumber()); contractualMissing.add(partner.getPartnerNumber());
} }
}); });
assertThat(contractualMissing).isEmpty(); // comment out if we do want to allow missing contractual contact
} }
private static boolean containsRole(final Record rec, final String role) { private static boolean containsRole(final Record rec, final String role) {
final var roles = rec.getString("roles"); final var roles = rec.getString("roles");

View File

@ -342,10 +342,6 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith
final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll())); final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll()));
final var givenRelationship = givenSomeTemporaryRelationshipBessler( final var givenRelationship = givenSomeTemporaryRelationshipBessler(
"Anita", "twelfth"); "Anita", "twelfth");
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll()).size()).as("unexpected number of roles created")
.isEqualTo(initialRoleNames.length + 3);
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()).size()).as("unexpected number of grants created")
.isEqualTo(initialGrantNames.length + 13);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {

View File

@ -1,7 +1,7 @@
contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles
# eine natürliche Person, implizites contractual # eine natürliche Person, implizites contractual
1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,billing,operation 1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,contractual,billing,operation
# eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner # eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner
1200; 20;; ; ; ; JM e.K.;; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner 1200; 20;; ; ; ; JM e.K.;; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner

1 contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles
2 # eine natürliche Person, implizites contractual
3 1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,billing,operation 1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,contractual,billing,operation
4 # eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner
5 1200; 20;; ; ; ; JM e.K.;; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner
6 1201; 20; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing
7 1202; 20; Herr; Andrew; Meyer-Operation; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact,subscriber:operations-announce