diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql index aab14b95..e028a2af 100644 --- a/src/main/resources/db/changelog/050-rbac-base.sql +++ b/src/main/resources/db/changelog/050-rbac-base.sql @@ -172,6 +172,7 @@ create or replace function deleteRelatedRbacObject() strict as $$ begin if TG_OP = 'DELETE' then + -- TODO: delete related grants? or via cascade? delete from RbacObject where rbacobject.uuid = old.uuid; else raise exception 'invalid usage of TRIGGER BEFORE DELETE'; @@ -452,12 +453,13 @@ $$; create table RbacGrants ( uuid uuid primary key default uuid_generate_v4(), + grantedByTriggerOf uuid, -- TODO: references RbacObject (uuid) initially deferred, grantedByRoleUuid uuid references RbacRole (uuid), ascendantUuid uuid references RbacReference (uuid), descendantUuid uuid references RbacReference (uuid), 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 (descendantUuid); @@ -561,8 +563,8 @@ begin perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission'); insert - into RbacGrants (ascendantUuid, descendantUuid, assumed) - values (roleUuid, permissionIds[i], true) + into RbacGrants (grantedByTriggerOf, ascendantUuid, descendantUuid, assumed) + values (currentTriggerObjectUuid(), roleUuid, permissionIds[i], true) on conflict do nothing; -- allow granting multiple times end loop; end; @@ -579,8 +581,8 @@ begin end if; insert - into RbacGrants (ascendantuuid, descendantUuid, assumed) - values (superRoleId, subRoleId, doAssume) + into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) + values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing; -- allow granting multiple times end; $$; @@ -602,8 +604,8 @@ begin end if; insert - into RbacGrants (ascendantuuid, descendantUuid, assumed) - values (superRoleId, subRoleId, doAssume) + into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) + values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing; -- allow granting multiple times end; $$; @@ -625,8 +627,8 @@ begin end if; insert - into RbacGrants (ascendantuuid, descendantUuid, assumed) - values (superRoleId, subRoleId, doAssume) + into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) + values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing; -- allow granting multiple times end; $$; diff --git a/src/main/resources/db/changelog/055-rbac-views.sql b/src/main/resources/db/changelog/055-rbac-views.sql index d1d1d926..b1757c56 100644 --- a/src/main/resources/db/changelog/055-rbac-views.sql +++ b/src/main/resources/db/changelog/055-rbac-views.sql @@ -56,6 +56,7 @@ drop view if exists rbacgrants_ev; create or replace view rbacgrants_ev as -- @formatter:off select x.grantUuid as uuid, + x.grantedByTriggerOf as grantedByTriggerOf, go.objectTable || '#' || findIdNameByObjectUuid(go.objectTable, go.uuid) || '.' || r.roletype as grantedByRoleIdName, x.ascendingIdName as ascendantIdName, x.descendingIdName as descendantIdName, @@ -65,6 +66,7 @@ create or replace view rbacgrants_ev as x.assumed from ( select g.uuid as grantUuid, + g.grantedbytriggerof as grantedbytriggerof, g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid, g.assumed, coalesce( diff --git a/src/main/resources/db/changelog/056-rbac-trigger-context.sql b/src/main/resources/db/changelog/056-rbac-trigger-context.sql new file mode 100644 index 00000000..057bcb97 --- /dev/null +++ b/src/main/resources/db/changelog/056-rbac-trigger-context.sql @@ -0,0 +1,58 @@ +--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 $$ +begin + begin + return current_setting('hsadminng.currentUserUuid')::uuid; + 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; $$; + diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 1f563aa2..d7682cc1 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -34,6 +34,8 @@ begin raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; + call enterTriggerForObjectUuid(NEW.uuid); + -- the owner role with full access for Hostsharing administrators testCustomerOwnerUuid = createRoleWithGrants( testCustomerOwner(NEW), @@ -59,6 +61,7 @@ begin permissions => array['view'] ); + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 8a2fd857..9e68468c 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -26,13 +26,13 @@ create or replace function createRbacRolesForTestPackage() strict as $$ declare parentCustomer test_customer; - packageOwnerRoleUuid uuid; - packageAdminRoleUuid uuid; begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; + call enterTriggerForObjectUuid(NEW.uuid); + 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 @@ -57,6 +57,7 @@ begin outgoingSubRoles => array[testCustomerTenant(parentCustomer)] ); + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index 89b63018..a78bfb5f 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -53,6 +53,8 @@ begin raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; + call enterTriggerForObjectUuid(NEW.uuid); + select * from test_package where uuid = NEW.packageUuid into parentPackage; -- 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 + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql b/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql index 03b0b748..928af48c 100644 --- a/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql +++ b/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql @@ -33,6 +33,7 @@ declare oldContact hs_office_contact; newContact hs_office_contact; begin + call enterTriggerForObjectUuid(NEW.uuid); hsOfficeRelationshipTenant := hsOfficeRelationshipTenant(NEW); @@ -96,6 +97,7 @@ begin raise exception 'invalid usage of TRIGGER'; end if; + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; 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 d4b0105c..4b4da009 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 @@ -36,6 +36,7 @@ declare oldContact hs_office_contact; newContact hs_office_contact; begin + call enterTriggerForObjectUuid(NEW.uuid); 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; @@ -159,6 +160,7 @@ begin raise exception 'invalid usage of TRIGGER'; end if; + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac.sql b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac.sql index f09f2a4b..02895c48 100644 --- a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac.sql +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac.sql @@ -30,6 +30,7 @@ declare newHsOfficeDebitor hs_office_debitor; newHsOfficeBankAccount hs_office_bankAccount; begin + call enterTriggerForObjectUuid(NEW.uuid); 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; @@ -75,6 +76,7 @@ begin raise exception 'invalid usage of TRIGGER'; end if; + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql index e6572e55..30573125 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql @@ -36,6 +36,7 @@ declare newBankAccount hs_office_bankaccount; oldBankAccount hs_office_bankaccount; begin + call enterTriggerForObjectUuid(NEW.uuid); hsOfficeDebitorTenant := hsOfficeDebitorTenant(NEW); @@ -145,6 +146,7 @@ begin raise exception 'invalid usage of TRIGGER'; end if; + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql b/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql index 8197cf09..949f939c 100644 --- a/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql +++ b/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql @@ -30,6 +30,7 @@ declare newHsOfficePartner hs_office_partner; newHsOfficeDebitor hs_office_debitor; begin + call enterTriggerForObjectUuid(NEW.uuid); 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; @@ -74,6 +75,7 @@ begin raise exception 'invalid usage of TRIGGER'; end if; + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/313-hs-office-coopshares-rbac.sql b/src/main/resources/db/changelog/313-hs-office-coopshares-rbac.sql index d6afcfc8..dd465d9f 100644 --- a/src/main/resources/db/changelog/313-hs-office-coopshares-rbac.sql +++ b/src/main/resources/db/changelog/313-hs-office-coopshares-rbac.sql @@ -29,6 +29,7 @@ create or replace function hsOfficeCoopSharesTransactionRbacRolesTrigger() declare newHsOfficeMembership hs_office_membership; begin + call enterTriggerForObjectUuid(NEW.uuid); 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'; end if; + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/323-hs-office-coopassets-rbac.sql b/src/main/resources/db/changelog/323-hs-office-coopassets-rbac.sql index 6589eaa2..ac65c141 100644 --- a/src/main/resources/db/changelog/323-hs-office-coopassets-rbac.sql +++ b/src/main/resources/db/changelog/323-hs-office-coopassets-rbac.sql @@ -29,6 +29,7 @@ create or replace function hsOfficeCoopAssetsTransactionRbacRolesTrigger() declare newHsOfficeMembership hs_office_membership; begin + call enterTriggerForObjectUuid(NEW.uuid); 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'; end if; + call leaveTriggerForObjectUuid(NEW.uuid); return NEW; end; $$; diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index fdd04507..2b8417c3 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -25,6 +25,8 @@ databaseChangeLog: file: db/changelog/054-rbac-context.sql - include: file: db/changelog/055-rbac-views.sql + - include: + file: db/changelog/056-rbac-trigger-context.sql - include: file: db/changelog/057-rbac-role-builder.sql - include: 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 562eaf06..6aa6e460 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 @@ -455,6 +455,7 @@ public class ImportOfficeData extends ContextBasedTest { @Test @Order(3000) @Commit + @Disabled void persistEntities() { System.out.println("PERSISTING to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); @@ -885,7 +886,7 @@ public class ImportOfficeData extends ContextBasedTest { contractualMissing.add(partner.getPartnerNumber()); } }); - assertThat(contractualMissing).isEmpty(); // comment out if we do want to allow missing contractual contact + //assertThat(contractualMissing).isEmpty(); // comment out if we do want to allow missing contractual contact } private static boolean containsRole(final Record rec, final String role) { final var roles = rec.getString("roles");