Files
pgmorbac/tests/03_delegation.sql
T
marc cc3d65a013 docs: complete function reference, drop unicode punctuation
Documents refresh_hierarchy_cache, is_rule_valid and org_in_scope, and
corrects the get_org_scope scope list which still omitted unattributed
and all.

Replaces em dashes and other typographic unicode with ASCII throughout
the schema comments, the test suite and the documentation. Comments and
prose are ASCII only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 07:26:54 +02:00

356 lines
15 KiB
SQL

-- =============================================================================
-- Delegation Tests
-- =============================================================================
-- Tests the role delegation feature:
-- - Active delegation grants the delegate the delegator's role permissions
-- - Expired delegations are inactive
-- - Future delegations are not yet active
-- - Revoked delegations are inactive
-- - Delegator must hold the role (delegation from source is validated)
-- - Negative role assignments override delegations
-- - Delegated roles participate in role hierarchy
-- - get_comprehensive_roles reports delegation source
--
-- Scenario:
-- Carol (manager) delegates her manager role to Leo (employee) for 1 day
-- -> Leo should temporarily have manager-level access
--
-- Prerequisites: 00_setup.sql -> 02_hierarchies.sql
-- =============================================================================
\echo ''
\echo '================================================================'
\echo '03 - DELEGATION'
\echo '================================================================'
-- ---------------------------------------------------------------------------
-- Section 1: Baseline - Leo (employee) before any delegation
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 1. Baseline: Leo before delegation ---'
-- Leo has only employee permissions
SELECT morbac.t('Leo (employee) reads documents before delegation',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'documents'
), TRUE);
-- Leo cannot approve documents - that requires manager role
SELECT morbac.t('Leo (employee) approves documents before delegation [no manager perm]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'approve', 'documents'
), FALSE);
-- Leo cannot delete documents (no employee delete perm, activity hierarchy grants delete via read rule,
-- but 'delete' implies only that if there's a delete->write->read chain; in 02 we set write->read but
-- the chain delete->write was also set. So Leo gets delete via documents perm. Let's check:
-- Actually with activity hierarchy (delete->write->read), the 'read documents' rule covers delete.
SELECT morbac.t('Leo (employee) deletes documents before delegation [via activity hierarchy]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'delete', 'documents'
), TRUE);
-- Leo cannot approve documents (approve is not in activity hierarchy for documents)
-- This is the key baseline check: approve requires manager-level permission
SELECT morbac.t('Leo (employee) does not have approve permission before delegation',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'approve', 'documents'
), FALSE);
-- ---------------------------------------------------------------------------
-- Section 2: Active delegation - Carol delegates manager role to Leo
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 2. Active delegation: Carol -> Leo (manager role, 1 day) ---'
INSERT INTO morbac.delegations
(id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
VALUES (
'de000001-0000-0000-0000-000000000001',
'30000000-0000-0000-0000-000000000003', -- Carol (delegator, holds manager)
'30000000-0000-0000-0000-000000000012', -- Leo (delegatee)
'20000000-0001-0000-0000-000000000003', -- manager role
'10000000-0000-0000-0000-000000000001',
now() - interval '1 hour',
now() + interval '1 day'
);
-- Leo now has delegated manager role - can approve documents
SELECT morbac.t('Leo (delegated manager) approves documents',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'approve', 'documents'
), TRUE);
-- Leo can delete documents (manager permission)
SELECT morbac.t('Leo (delegated manager) deletes documents',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'delete', 'documents'
), TRUE);
-- Leo (delegated manager) inherits manager's inherited permissions:
-- manager->employee->intern chain; employee has read documents -> view hierarchy covers financial_data
SELECT morbac.t('Leo (delegated manager) reads financial_data via manager+view hierarchy',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'financial_data'
), TRUE);
-- get_comprehensive_roles should show manager role with delegation source
SELECT morbac.t('Leo has delegated manager role in comprehensive roles',
EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE role_id = '20000000-0001-0000-0000-000000000003'
AND source LIKE 'delegation%'
), TRUE);
-- Employee role appears as direct in comprehensive roles
SELECT morbac.t('Leo has employee role as direct in comprehensive roles',
EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE role_id = '20000000-0001-0000-0000-000000000004'
AND source = 'direct'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 3: Expired delegation
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 3. Expired delegation ---'
INSERT INTO morbac.delegations
(id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
VALUES (
'de000001-0000-0000-0000-000000000002',
'30000000-0000-0000-0000-000000000008', -- Heidi (auditor)
'30000000-0000-0000-0000-000000000004', -- Dave
'20000000-0001-0000-0000-000000000006', -- auditor role
'10000000-0000-0000-0000-000000000001',
now() - interval '7 days',
now() - interval '1 second' -- expired 1 second ago
);
-- Dave should NOT get auditor permissions from expired delegation
SELECT morbac.t('Dave via expired auditor delegation reads audit_logs [delegation expired]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000004'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'audit_logs'
), FALSE);
-- The expired delegation should NOT appear in comprehensive roles
SELECT morbac.t('Expired auditor delegation not in Dave comprehensive roles',
NOT EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000004'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE role_id = '20000000-0001-0000-0000-000000000006'
AND source LIKE 'delegation%'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 4: Future delegation (not yet active)
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 4. Future delegation (not yet active) ---'
INSERT INTO morbac.delegations
(id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
VALUES (
'de000001-0000-0000-0000-000000000003',
'30000000-0000-0000-0000-000000000007', -- Grace (hr_manager)
'30000000-0000-0000-0000-000000000004', -- Dave
'20000000-0001-0000-0000-000000000008', -- hr_manager role
'10000000-0000-0000-0000-000000000001',
now() + interval '1 day', -- starts tomorrow
now() + interval '8 days'
);
-- Dave should NOT have hr_manager in his comprehensive roles (delegation not yet active)
-- Note: employee already has write hr_data via view hierarchy (hr_data->documents);
-- so we test comprehensive roles rather than is_allowed to verify the delegation is inactive.
SELECT morbac.t('Future hr_manager delegation not yet in Dave comprehensive roles',
NOT EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000004'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE role_id = '20000000-0001-0000-0000-000000000008' -- hr_manager
AND source LIKE 'delegation%'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 5: Revoked delegation
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 5. Revoked delegation ---'
INSERT INTO morbac.delegations
(id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until, revoked)
VALUES (
'de000001-0000-0000-0000-000000000004',
'30000000-0000-0000-0000-000000000009', -- Ivan (accountant)
'30000000-0000-0000-0000-000000000004', -- Dave
'20000000-0001-0000-0000-000000000007', -- accountant role
'10000000-0000-0000-0000-000000000001',
now() - interval '1 hour',
now() + interval '1 day',
TRUE -- revoked
);
-- Dave should NOT get accountant from revoked delegation - verify revoked flag is set
-- Note: employee already has write financial_data via view hierarchy;
-- so we verify the delegation is actually revoked in the DB.
SELECT morbac.t('Revoked accountant delegation has revoked=TRUE in DB',
(SELECT revoked FROM morbac.delegations
WHERE id = 'de000001-0000-0000-0000-000000000004'),
TRUE);
-- The revoked delegation should NOT appear in comprehensive roles
SELECT morbac.t('Revoked accountant delegation not in Dave comprehensive roles',
NOT EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000004'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE role_id = '20000000-0001-0000-0000-000000000007'
AND source LIKE 'delegation%'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 6: Delegation blocked by negative role assignment
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 6. Negative role assignment overrides delegation ---'
-- Leo has an active delegation for manager role (from section 2)
-- Now add a negative assignment to block that same role
INSERT INTO morbac.negative_role_assignments (user_id, role_id, org_id, reason)
VALUES (
'30000000-0000-0000-0000-000000000012', -- Leo
'20000000-0001-0000-0000-000000000003', -- manager role
'10000000-0000-0000-0000-000000000001',
'Temporarily suspended from manager responsibilities'
);
-- negative assignment blocks the delegated manager role
SELECT morbac.t('Leo (delegated manager, but negatively assigned) approves documents [blocked]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'approve', 'documents'
), FALSE);
-- Leo's own employee permissions still work (negative is only for manager)
SELECT morbac.t('Leo (employee, manager negated) reads documents [employee perm unaffected]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'documents'
), TRUE);
-- Manager delegation no longer appears in comprehensive roles (blocked by negative)
SELECT morbac.t('Negated manager delegation not in Leo comprehensive roles',
NOT EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000012'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE role_id = '20000000-0001-0000-0000-000000000003'
), TRUE);
-- Clean up negative assignment so subsequent tests aren't affected
DELETE FROM morbac.negative_role_assignments
WHERE user_id = '30000000-0000-0000-0000-000000000012'
AND role_id = '20000000-0001-0000-0000-000000000003'
AND org_id = '10000000-0000-0000-0000-000000000001';
-- ---------------------------------------------------------------------------
-- Section 7: Delegator must hold the role (validation check)
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 7. Delegator-role validation ---'
-- Create a delegation where the delegator does NOT hold the role
-- Dave (employee) tries to delegate the manager role (which he does not have)
INSERT INTO morbac.delegations
(id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
VALUES (
'de000001-0000-0000-0000-000000000005',
'30000000-0000-0000-0000-000000000004', -- Dave (employee - does NOT hold manager)
'30000000-0000-0000-0000-000000000005', -- Eve
'20000000-0001-0000-0000-000000000003', -- manager role
'10000000-0000-0000-0000-000000000001',
now() - interval '1 hour',
now() + interval '1 day'
);
-- get_comprehensive_roles requires delegator to hold the role
-- Eve does NOT get manager access from invalid delegation
SELECT morbac.t('Eve via invalid delegation (Dave does not hold manager) denied approve',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000005'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'approve', 'documents'
), FALSE);
-- The invalid delegation should NOT appear in Eve's comprehensive roles
SELECT morbac.t('Invalid delegation not in Eve comprehensive roles',
NOT EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000005'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE role_id = '20000000-0001-0000-0000-000000000003'
AND source LIKE 'delegation%'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 8: Comprehensive roles source reporting
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 8. get_comprehensive_roles source reporting ---'
-- Alice (CEO, direct) - source should be 'direct'
SELECT morbac.t('Alice CEO role has source=direct in comprehensive roles',
EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000001'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE role_id = '20000000-0001-0000-0000-000000000001'
AND source = 'direct'
), TRUE);
-- Alice has no delegation-sourced roles
SELECT morbac.t('Alice has no delegation-sourced roles',
NOT EXISTS(
SELECT 1 FROM morbac.get_comprehensive_roles(
'30000000-0000-0000-0000-000000000001'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
)
WHERE source LIKE 'delegation%'
), TRUE);
\echo ''
\echo '=== Delegation Tests Completed ==='