-- ============================================================================= -- Test Script for morbac_pg Extension -- ============================================================================= -- This script demonstrates and tests the Multi-OrBAC implementation -- -- Test Scenario: -- - Two organizations: "Acme Corp" and "Beta Inc" -- - Multiple users with different roles -- - Permission and prohibition rules -- - Context evaluation -- - Multi-organization resource access -- - Obligations and recommendations -- ============================================================================= -- Clean up any previous test DROP EXTENSION IF EXISTS morbac_pg CASCADE; DROP SCHEMA IF EXISTS morbac CASCADE; -- Install the extension CREATE EXTENSION morbac_pg; -- Verify schema and tables exist \echo '=== Schema and Tables Created ===' SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'morbac' ORDER BY tablename; \echo '' \echo '=== Test 1: Create Organizations ===' -- Create two organizations INSERT INTO morbac.orgs (id, name) VALUES ('11111111-1111-1111-1111-111111111111', 'Acme Corp'), ('22222222-2222-2222-2222-222222222222', 'Beta Inc'); -- Create hierarchical organization (Acme Subsidiary under Acme Corp) INSERT INTO morbac.orgs (id, name, parent_id) VALUES ('33333333-3333-3333-3333-333333333333', 'Acme Subsidiary', '11111111-1111-1111-1111-111111111111'); SELECT o.name, COALESCE(p.name, 'None') as parent_organization FROM morbac.orgs o LEFT JOIN morbac.orgs p ON o.parent_id = p.id ORDER BY o.parent_id NULLS FIRST, o.name; \echo '' \echo '=== Test 2: Create Roles ===' -- Create roles in Acme Corp INSERT INTO morbac.roles (id, org_id, name, description) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', 'admin', 'Administrator role'), ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-1111-1111-1111-111111111111', 'employee', 'Regular employee'), ('cccccccc-cccc-cccc-cccc-cccccccccccc', '11111111-1111-1111-1111-111111111111', 'contractor', 'External contractor'), ('ffffffff-ffff-ffff-ffff-ffffffffffff', '11111111-1111-1111-1111-111111111111', 'viewer', 'Read-only viewer'); -- Create roles in Beta Inc INSERT INTO morbac.roles (id, org_id, name, description) VALUES ('dddddddd-dddd-dddd-dddd-dddddddddddd', '22222222-2222-2222-2222-222222222222', 'manager', 'Manager role'), ('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', '22222222-2222-2222-2222-222222222222', 'staff', 'Staff member'); SELECT r.name as role, o.name as organization FROM morbac.roles r JOIN morbac.orgs o ON r.org_id = o.id ORDER BY o.name, r.name; \echo '' \echo '=== Test 2b: Create Role Hierarchy ===' -- Role hierarchy in Acme Corp: -- admin > employee > viewer -- (admin inherits from employee, employee inherits from viewer) INSERT INTO morbac.role_hierarchy (senior_role_id, junior_role_id) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'), -- admin inherits from employee ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'ffffffff-ffff-ffff-ffff-ffffffffffff'); -- employee inherits from viewer \echo 'Role hierarchy created:' SELECT sr.name as senior_role, jr.name as junior_role, o.name as organization FROM morbac.role_hierarchy rh JOIN morbac.roles sr ON rh.senior_role_id = sr.id JOIN morbac.roles jr ON rh.junior_role_id = jr.id JOIN morbac.orgs o ON sr.org_id = o.id; \echo '' \echo '=== Test 3: Assign Users to Roles ===' -- Define test users (in real scenario, these would come from auth system) -- User Alice (admin at Acme) INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES ('aaaaaaaa-0000-0000-0000-000000000001', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111'); -- User Bob (employee at Acme) INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES ('bbbbbbbb-0000-0000-0000-000000000002', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-1111-1111-1111-111111111111'); -- User Charlie (contractor at Acme AND staff at Beta - multi-org user) INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES ('cccccccc-0000-0000-0000-000000000003', 'cccccccc-cccc-cccc-cccc-cccccccccccc', '11111111-1111-1111-1111-111111111111'), ('cccccccc-0000-0000-0000-000000000003', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', '22222222-2222-2222-2222-222222222222'); -- User Diana (manager at Beta) INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES ('dddddddd-0000-0000-0000-000000000004', 'dddddddd-dddd-dddd-dddd-dddddddddddd', '22222222-2222-2222-2222-222222222222'); SELECT ur.user_id, r.name as role, o.name as organization FROM morbac.user_roles ur JOIN morbac.roles r ON ur.role_id = r.id JOIN morbac.orgs o ON ur.org_id = o.id ORDER BY ur.user_id; \echo '' \echo '=== Test 4: Define Activities and Views ===' INSERT INTO morbac.activities (name, description) VALUES ('read', 'Read/view data'), ('write', 'Create/modify data'), ('delete', 'Delete data'), ('approve', 'Approve actions'); INSERT INTO morbac.views (name, description) VALUES ('documents', 'Document resources'), ('reports', 'Report resources'), ('sensitive_data', 'Sensitive/confidential data'), ('temp_documents', 'Temporary documents'), ('future_documents', 'Future documents'), ('active_documents', 'Active documents'); SELECT * FROM morbac.activities ORDER BY name; SELECT * FROM morbac.views ORDER BY name; \echo '' \echo '=== Test 5: Create Additional Contexts ===' -- Business hours context (simplified - always true for this test) CREATE OR REPLACE FUNCTION morbac.context_business_hours() RETURNS BOOLEAN LANGUAGE plpgsql STABLE AS $$ BEGIN -- In production, check current time against business hours -- For test, return TRUE RETURN TRUE; END; $$; INSERT INTO morbac.contexts (name, description, evaluator) VALUES ('business_hours', 'During business hours', 'morbac.context_business_hours'::regproc); -- Weekend context (always false for test) CREATE OR REPLACE FUNCTION morbac.context_weekend() RETURNS BOOLEAN LANGUAGE plpgsql STABLE AS $$ BEGIN -- In production, check if today is weekend -- For test, return FALSE RETURN FALSE; END; $$; INSERT INTO morbac.contexts (name, description, evaluator) VALUES ('weekend', 'During weekend', 'morbac.context_weekend'::regproc); SELECT name, description FROM morbac.contexts ORDER BY name; \echo '' \echo '=== Test 6: Define Policy Using DSL ===' -- Acme Corp policies INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES -- Admins can do everything on documents ('Acme Corp', 'admin', 'read', 'documents', 'permission', 'always'), ('Acme Corp', 'admin', 'write', 'documents', 'permission', 'always'), ('Acme Corp', 'admin', 'delete', 'documents', 'permission', 'always'), -- Employees can read and write documents ('Acme Corp', 'employee', 'read', 'documents', 'permission', 'always'), ('Acme Corp', 'employee', 'write', 'documents', 'permission', 'business_hours'), -- Contractors can only read documents ('Acme Corp', 'contractor', 'read', 'documents', 'permission', 'always'), -- PROHIBITION: Contractors cannot access sensitive data ('Acme Corp', 'contractor', 'read', 'sensitive_data', 'prohibition', 'always'), ('Acme Corp', 'contractor', 'write', 'sensitive_data', 'prohibition', 'always'), -- OBLIGATION: Employees must review reports weekly ('Acme Corp', 'employee', 'read', 'reports', 'obligation', 'always'), -- Admins can access reports ('Acme Corp', 'admin', 'read', 'reports', 'permission', 'always'), ('Acme Corp', 'admin', 'approve', 'reports', 'permission', 'always'); -- Beta Inc policies INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES -- Managers have full access ('Beta Inc', 'manager', 'read', 'documents', 'permission', 'always'), ('Beta Inc', 'manager', 'write', 'documents', 'permission', 'always'), ('Beta Inc', 'manager', 'read', 'reports', 'permission', 'always'), -- Staff can read documents ('Beta Inc', 'staff', 'read', 'documents', 'permission', 'always'), -- PROHIBITION: No document modifications on weekends (example) ('Beta Inc', 'staff', 'write', 'documents', 'prohibition', 'weekend'), -- RECOMMENDATION: Staff should review reports ('Beta Inc', 'staff', 'read', 'reports', 'recommendation', 'always'); SELECT org_name, role_name, activity, view, modality, context_name FROM morbac.policy ORDER BY org_name, role_name, modality; \echo '' \echo '=== Test 7: Compile Policy ===' SELECT * FROM morbac.compile_policy(); -- Verify rules were created \echo '' \echo 'Rules created by policy compiler:' SELECT o.name as org, r.name as role, ru.activity, ru.view, ru.modality, c.name as context FROM morbac.rules ru JOIN morbac.orgs o ON ru.org_id = o.id JOIN morbac.roles r ON ru.role_id = r.id JOIN morbac.contexts c ON ru.context_id = c.id ORDER BY o.name, r.name, ru.modality, ru.activity; \echo '' \echo '=== Test 8: Authorization Decisions ===' -- Test Alice (admin at Acme) \echo 'Alice (admin at Acme Corp):' SELECT 'read documents' as action, morbac.is_allowed( 'aaaaaaaa-0000-0000-0000-000000000001'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'read', 'documents' ) as allowed; SELECT 'delete documents' as action, morbac.is_allowed( 'aaaaaaaa-0000-0000-0000-000000000001'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'delete', 'documents' ) as allowed; -- Test Bob (employee at Acme) \echo '' \echo 'Bob (employee at Acme Corp):' SELECT 'read documents' as action, morbac.is_allowed( 'bbbbbbbb-0000-0000-0000-000000000002'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'read', 'documents' ) as allowed; SELECT 'delete documents' as action, morbac.is_allowed( 'bbbbbbbb-0000-0000-0000-000000000002'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'delete', 'documents' ) as allowed; -- Test Charlie (contractor at Acme) \echo '' \echo 'Charlie (contractor at Acme Corp):' SELECT 'read documents' as action, morbac.is_allowed( 'cccccccc-0000-0000-0000-000000000003'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'read', 'documents' ) as allowed; SELECT 'write documents' as action, morbac.is_allowed( 'cccccccc-0000-0000-0000-000000000003'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'write', 'documents' ) as allowed; -- Test PROHIBITION precedence \echo '' \echo 'Testing PROHIBITION PRECEDENCE:' \echo 'Charlie (contractor) trying to read sensitive_data (should be DENIED by prohibition):' SELECT 'read sensitive_data' as action, morbac.is_allowed( 'cccccccc-0000-0000-0000-000000000003'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'read', 'sensitive_data' ) as allowed; -- Test Charlie at Beta Inc (multi-org user) \echo '' \echo 'Charlie as staff at Beta Inc:' SELECT 'read documents' as action, morbac.is_allowed( 'cccccccc-0000-0000-0000-000000000003'::uuid, '22222222-2222-2222-2222-222222222222'::uuid, 'read', 'documents' ) as allowed; -- Test Diana (manager at Beta) \echo '' \echo 'Diana (manager at Beta Inc):' SELECT 'write documents' as action, morbac.is_allowed( 'dddddddd-0000-0000-0000-000000000004'::uuid, '22222222-2222-2222-2222-222222222222'::uuid, 'write', 'documents' ) as allowed; \echo '' \echo '=== Test 8b: Role Hierarchy Inheritance ===' -- Assign user Eve only viewer role \echo 'Creating user Eve with only viewer role at Acme:' INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES ('eeeeeeee-0000-0000-0000-000000000005', 'ffffffff-ffff-ffff-ffff-ffffffffffff', '11111111-1111-1111-1111-111111111111'); -- Add permission for viewer role INSERT INTO morbac.policy (org_name, role_name, activity, view, modality) VALUES ('Acme Corp', 'viewer', 'read', 'documents', 'permission'); SELECT * FROM morbac.compile_policy(); \echo '' \echo 'Eve (has viewer role, which employee inherits from):' SELECT 'read documents' as action, morbac.is_allowed( 'eeeeeeee-0000-0000-0000-000000000005'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'read', 'documents' ) as allowed; \echo '' \echo 'Bob (employee) should inherit permissions from viewer role:' \echo 'Effective roles for Bob (should include employee and viewer via hierarchy):' SELECT r.name, er.depth FROM morbac.get_effective_roles( 'bbbbbbbb-0000-0000-0000-000000000002'::uuid, '11111111-1111-1111-1111-111111111111'::uuid ) er JOIN morbac.roles r ON er.role_id = r.id ORDER BY er.depth; \echo '' \echo 'Alice (admin) should inherit from both employee and viewer:' SELECT r.name, er.depth FROM morbac.get_effective_roles( 'aaaaaaaa-0000-0000-0000-000000000001'::uuid, '11111111-1111-1111-1111-111111111111'::uuid ) er JOIN morbac.roles r ON er.role_id = r.id ORDER BY er.depth; \echo '' \echo '=== Test 8c: Organization Hierarchy ===' \echo 'Ancestors of Acme Subsidiary:' SELECT o.name, oa.depth FROM morbac.get_org_ancestors('33333333-3333-3333-3333-333333333333'::uuid) oa JOIN morbac.orgs o ON oa.org_id = o.id ORDER BY oa.depth; \echo '' \echo 'Descendants of Acme Corp (should include subsidiary):' SELECT o.name, od.depth FROM morbac.get_org_descendants('11111111-1111-1111-1111-111111111111'::uuid) od JOIN morbac.orgs o ON od.org_id = o.id ORDER BY od.depth; \echo '' \echo '=== Test 9: Obligations and Recommendations ===' \echo 'Obligations for Bob (employee at Acme):' SELECT * FROM morbac.pending_obligations( 'bbbbbbbb-0000-0000-0000-000000000002'::uuid, '11111111-1111-1111-1111-111111111111'::uuid ); \echo '' \echo 'Recommendations for Charlie (staff at Beta):' SELECT * FROM morbac.pending_recommendations( 'cccccccc-0000-0000-0000-000000000003'::uuid, '22222222-2222-2222-2222-222222222222'::uuid ); \echo '' \echo '=== Test 10: Utility Functions ===' \echo 'Roles for Charlie at Acme Corp:' SELECT * FROM morbac.user_roles_in_org( 'cccccccc-0000-0000-0000-000000000003'::uuid, '11111111-1111-1111-1111-111111111111'::uuid ); \echo '' \echo 'Roles for Charlie at Beta Inc:' SELECT * FROM morbac.user_roles_in_org( 'cccccccc-0000-0000-0000-000000000003'::uuid, '22222222-2222-2222-2222-222222222222'::uuid ); \echo '' \echo 'Does Alice have admin role at Acme Corp?' SELECT morbac.user_has_role( 'aaaaaaaa-0000-0000-0000-000000000001'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'admin' ) as has_admin_role; \echo '' \echo '=== Test 11: Context Evaluation ===' \echo 'Evaluating always context:' SELECT morbac.eval_context( (SELECT id FROM morbac.contexts WHERE name = 'always') ) as result; \echo '' \echo 'Evaluating business_hours context:' SELECT morbac.eval_context( (SELECT id FROM morbac.contexts WHERE name = 'business_hours') ) as result; \echo '' \echo 'Evaluating weekend context:' SELECT morbac.eval_context( (SELECT id FROM morbac.contexts WHERE name = 'weekend') ) as result; \echo '' \echo '=== Test 12: Policy Re-compilation (Idempotency Test) ===' \echo 'Compiling policy again (should show 0 new rules, idempotent):' SELECT * FROM morbac.compile_policy(); \echo '' \echo '=== Test 13: Adding New Policy Entry ===' INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES ('Acme Corp', 'admin', 'approve', 'documents', 'permission', 'always'); \echo 'Compiling new policy entry:' SELECT * FROM morbac.compile_policy(); \echo '' \echo '=== Test 14: Delegation ===' -- Alice delegates admin role to user Frank temporarily INSERT INTO morbac.delegations (delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until) VALUES ('aaaaaaaa-0000-0000-0000-000000000001', 'ffffffff-0000-0000-0000-000000000006', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', now(), now() + interval '1 day'); \echo 'Frank (via delegation from Alice) should have admin permissions:' SELECT 'delete documents (delegated)' as action, morbac.is_allowed( 'ffffffff-0000-0000-0000-000000000006'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'delete', 'documents' ) as allowed; \echo 'Frank effective roles (should include delegated admin):' SELECT r.name, cr.source FROM morbac.get_comprehensive_roles('ffffffff-0000-0000-0000-000000000006'::uuid, '11111111-1111-1111-1111-111111111111'::uuid) cr JOIN morbac.roles r ON cr.role_id = r.id; \echo '' \echo '=== Test 15: Separation of Duty ===' -- Define SoD conflict: auditor and accountant roles are mutually exclusive INSERT INTO morbac.roles (id, org_id, name, description) VALUES ('11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111', 'auditor', 'Auditor role'), ('22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'accountant', 'Accountant role'); INSERT INTO morbac.sod_conflicts (role_a_id, role_b_id, org_id, description) VALUES ('11111111-1111-1111-1111-111111111111', '22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'Auditors and accountants are mutually exclusive'); -- Assign user to auditor role INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES ('99999999-0000-0000-0000-000000000009', '11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111'); \echo 'Check SoD violation if we try to assign accountant role to auditor:' SELECT morbac.check_sod_violation( '99999999-0000-0000-0000-000000000009'::uuid, '22222222-2222-2222-2222-222222222222'::uuid, '11111111-1111-1111-1111-111111111111'::uuid ) as would_violate_sod; \echo '' \echo '=== Test 16: Activity and View Hierarchies ===' -- Define activity hierarchy: write implies read INSERT INTO morbac.activity_hierarchy (senior_activity, junior_activity) VALUES ('write', 'read'); -- Define view hierarchy: sensitive_data is a type of documents INSERT INTO morbac.view_hierarchy (senior_view, junior_view) VALUES ('sensitive_data', 'documents'); \echo 'Activities implied by write:' SELECT * FROM morbac.get_effective_activities('write'); \echo 'Views for sensitive_data (should include documents):' SELECT * FROM morbac.get_effective_views('sensitive_data'); \echo 'Bob has permission for read+documents, so write+documents should work (activity hierarchy):' SELECT 'write documents (via activity hierarchy)' as action, morbac.is_allowed( 'bbbbbbbb-0000-0000-0000-000000000002'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'write', 'documents' ) as allowed; \echo '' \echo '=== Test 17: Negative Role Assignment ===' -- Explicitly prohibit contractor from ever being admin INSERT INTO morbac.negative_role_assignments (user_id, role_id, org_id, reason) VALUES ('cccccccc-0000-0000-0000-000000000003', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', 'Contractors cannot be administrators'); \echo 'Charlie cannot have admin role even if explicitly assigned (negative assignment takes precedence).' \echo '' \echo '=== Test 18: Cardinality Constraints ===' -- Set max 2 users for admin role INSERT INTO morbac.role_cardinality (role_id, min_users, max_users, description) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 1, 2, 'Admin role limited to 2 users minimum 1'); \echo 'Check cardinality when adding user (1 admin exists, max is 2, should be OK):' SELECT morbac.check_cardinality_violation('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, TRUE) as violation; \echo '' \echo '=== Test 19: Administration Rules ===' -- Only admin role can create rules INSERT INTO morbac.admin_rules (org_id, role_id, admin_activity, admin_target, context_id, modality) VALUES ('11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'create_rule', 'rules', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission'); \echo 'Alice (admin) can create rules:' SELECT morbac.is_admin_allowed( 'aaaaaaaa-0000-0000-0000-000000000001'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'create_rule', 'rules' ) as can_create_rules; \echo 'Bob (employee) cannot create rules:' SELECT morbac.is_admin_allowed( 'bbbbbbbb-0000-0000-0000-000000000002'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'create_rule', 'rules' ) as can_create_rules; \echo '' \echo '=== Test 20: Cross-Organization Rules ===' -- Allow Beta managers to read Acme documents (inter-org collaboration) INSERT INTO morbac.cross_org_rules (source_org_id, target_org_id, role_id, activity, view, context_id, modality) VALUES ('22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'dddddddd-dddd-dddd-dddd-dddddddddddd', 'read', 'documents', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission'); \echo 'Diana (manager at Beta) can read Acme documents via cross-org rule:' SELECT 'read Acme documents from Beta' as action, morbac.is_allowed( 'dddddddd-0000-0000-0000-000000000004'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'read', 'documents' ) as allowed; \echo '' \echo '=== Test 21: Temporal Constraints on Rules ===' -- Create a time-limited rule (expires in the past) INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_until) VALUES ( '11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', -- employee role 'write', 'temp_documents', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission', '2020-01-01 00:00:00+00' -- Expired rule ); -- Create a future rule (not yet active) INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from) VALUES ( '11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'write', 'future_documents', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission', '2030-01-01 00:00:00+00' -- Future rule ); -- Create an active time-bounded rule INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from, valid_until) VALUES ( '11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'write', 'active_documents', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission', '2020-01-01 00:00:00+00', -- Started in the past '2030-12-31 23:59:59+00' -- Ends in the future ); \echo 'Alice (employee) access to temporal-constrained documents:' SELECT 'write to temp_documents (expired rule)' as action, morbac.is_allowed( 'aaaaaaaa-0000-0000-0000-000000000001'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'write', 'temp_documents' ) as allowed UNION ALL SELECT 'write to future_documents (not yet active)' as action, morbac.is_allowed( 'aaaaaaaa-0000-0000-0000-000000000001'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'write', 'future_documents' ) as allowed UNION ALL SELECT 'write to active_documents (currently valid)' as action, morbac.is_allowed( 'aaaaaaaa-0000-0000-0000-000000000001'::uuid, '11111111-1111-1111-1111-111111111111'::uuid, 'write', 'active_documents' ) as allowed; \echo '' \echo '=== Test 22: Audit Logging ===' -- Enable audit on user_roles table SELECT morbac.enable_audit('user_roles'); \echo 'Audit trigger created for user_roles table' -- Perform some auditable operations INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES ( 'ffffffff-0000-0000-0000-000000000001'::uuid, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', -- employee role '11111111-1111-1111-1111-111111111111' ); -- Update an existing user_role UPDATE morbac.user_roles SET active = false WHERE user_id = 'aaaaaaaa-0000-0000-0000-000000000001'::uuid AND role_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; -- Delete a user role DELETE FROM morbac.user_roles WHERE user_id = 'ffffffff-0000-0000-0000-000000000001'::uuid AND role_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; \echo 'Audit log entries for user_roles operations:' SELECT operation, record_id, CASE WHEN old_data IS NOT NULL THEN old_data->>'active' ELSE 'N/A' END as old_active, CASE WHEN new_data IS NOT NULL THEN new_data->>'active' ELSE 'N/A' END as new_active, session_user, timestamp FROM morbac.audit_log WHERE table_name = 'user_roles' ORDER BY timestamp; \echo 'Disable audit on user_roles:' SELECT morbac.disable_audit('user_roles'); \echo '' \echo '=== ALL TESTS COMPLETED SUCCESSFULLY ===' \echo '' \echo 'Summary:' \echo '- Extension installed and schema created' \echo '- Organizations with hierarchy' \echo '- Roles with hierarchy' \echo '- Delegation support' \echo '- Separation of Duty constraints' \echo '- Activity and View hierarchies' \echo '- Negative role assignments' \echo '- Cardinality constraints' \echo '- Administration rules' \echo '- Cross-organizational rules' \echo '- Temporal constraints on rules (valid_from, valid_until)' \echo '- Audit logging for security-critical operations'