cc3d65a013
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>
281 lines
11 KiB
SQL
281 lines
11 KiB
SQL
-- =============================================================================
|
|
-- Audit Logging Tests
|
|
-- =============================================================================
|
|
-- Tests the audit system: enable_audit / disable_audit and the resulting
|
|
-- audit_log entries for INSERT, UPDATE, and DELETE operations.
|
|
--
|
|
-- Cases:
|
|
-- 1. Enable audit creates trigger on specified table
|
|
-- 2. INSERT to audited table creates an audit log entry
|
|
-- 3. DELETE from audited table creates an audit log entry
|
|
-- 4. UPDATE to audited table creates an audit log entry with changed_fields
|
|
-- 5. All operations for a record_id are queryable
|
|
-- 6. Multiple tables can be audited independently
|
|
-- 7. Disable audit removes the trigger
|
|
-- 8. After disabling, changes are no longer logged
|
|
--
|
|
-- Prerequisites: 00_setup.sql -> 06_cross_org.sql
|
|
-- =============================================================================
|
|
|
|
\echo ''
|
|
\echo '================================================================'
|
|
\echo '07 - AUDIT LOGGING'
|
|
\echo '================================================================'
|
|
|
|
-- Clear any existing audit log entries to start fresh
|
|
TRUNCATE morbac.audit_log;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 1: Enable audit on user_roles
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 1. Enable audit on user_roles ---'
|
|
|
|
SELECT morbac.enable_audit('user_roles');
|
|
|
|
-- Trigger must exist on user_roles after enable
|
|
SELECT morbac.t('Trigger exists on user_roles after enable_audit',
|
|
(SELECT COUNT(*) > 0 FROM information_schema.triggers
|
|
WHERE event_object_schema = 'morbac'
|
|
AND event_object_table = 'user_roles'),
|
|
TRUE);
|
|
|
|
-- No entries before any change
|
|
SELECT morbac.t_eq('Audit log count before operations = 0',
|
|
(SELECT COUNT(*) FROM morbac.audit_log WHERE table_name = 'user_roles')::bigint,
|
|
0);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 2: INSERT is logged
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 2. INSERT is logged ---'
|
|
|
|
-- Assign a new role to Karl (user who has no role)
|
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id)
|
|
VALUES (
|
|
'30000000-0000-0000-0000-000000000011', -- Karl
|
|
'20000000-0001-0000-0000-000000000005', -- intern role
|
|
'10000000-0000-0000-0000-000000000001'
|
|
);
|
|
|
|
-- Exactly 1 INSERT entry for user_roles
|
|
SELECT morbac.t_eq('Audit log has 1 INSERT entry after INSERT into user_roles',
|
|
(SELECT COUNT(*) FROM morbac.audit_log
|
|
WHERE table_name = 'user_roles' AND operation = 'INSERT')::bigint,
|
|
1);
|
|
|
|
-- new_data must contain the inserted record
|
|
SELECT morbac.t('INSERT audit entry contains user_id in new_data',
|
|
(SELECT new_data->>'user_id' FROM morbac.audit_log
|
|
WHERE table_name = 'user_roles' AND operation = 'INSERT'
|
|
LIMIT 1) = '30000000-0000-0000-0000-000000000011',
|
|
TRUE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 3: DELETE is logged
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 3. DELETE is logged ---'
|
|
|
|
DELETE FROM morbac.user_roles
|
|
WHERE user_id = '30000000-0000-0000-0000-000000000011'
|
|
AND role_id = '20000000-0001-0000-0000-000000000005'
|
|
AND org_id = '10000000-0000-0000-0000-000000000001';
|
|
|
|
-- Now 2 entries: INSERT + DELETE
|
|
SELECT morbac.t_eq('Audit log has 2 entries after DELETE (INSERT + DELETE)',
|
|
(SELECT COUNT(*) FROM morbac.audit_log WHERE table_name = 'user_roles')::bigint,
|
|
2);
|
|
|
|
-- DELETE entry must have old_data populated
|
|
SELECT morbac.t('DELETE audit entry has old_data populated',
|
|
(SELECT old_data IS NOT NULL FROM morbac.audit_log
|
|
WHERE table_name = 'user_roles' AND operation = 'DELETE'
|
|
LIMIT 1),
|
|
TRUE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 4: Enable audit on rules and test UPDATE logging
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 4. UPDATE on rules table is logged (with changed_fields) ---'
|
|
|
|
SELECT morbac.enable_audit('rules');
|
|
|
|
-- Insert a rule to audit (using explicit id for record_id tracking)
|
|
INSERT INTO morbac.rules (id, org_id, role_id, activity, view, context_id, modality)
|
|
VALUES (
|
|
'a0000000-0000-0000-0000-000000000001',
|
|
'10000000-0000-0000-0000-000000000001',
|
|
'20000000-0001-0000-0000-000000000009', -- contractor
|
|
'read', 'public_data',
|
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
'permission'
|
|
);
|
|
|
|
-- 1 INSERT entry for rules
|
|
SELECT morbac.t_eq('Audit log has 1 INSERT entry for rules',
|
|
(SELECT COUNT(*) FROM morbac.audit_log
|
|
WHERE table_name = 'rules' AND operation = 'INSERT')::bigint,
|
|
1);
|
|
|
|
-- Update the modality (simulate a policy change)
|
|
UPDATE morbac.rules
|
|
SET modality = 'prohibition'
|
|
WHERE id = 'a0000000-0000-0000-0000-000000000001';
|
|
|
|
-- 1 UPDATE entry with changed_fields
|
|
SELECT morbac.t_eq('Audit log has 1 UPDATE entry for rules',
|
|
(SELECT COUNT(*) FROM morbac.audit_log
|
|
WHERE table_name = 'rules' AND operation = 'UPDATE')::bigint,
|
|
1);
|
|
|
|
-- changed_fields must be non-null for UPDATE
|
|
SELECT morbac.t('UPDATE audit entry has changed_fields populated',
|
|
(SELECT changed_fields IS NOT NULL FROM morbac.audit_log
|
|
WHERE table_name = 'rules' AND operation = 'UPDATE'
|
|
LIMIT 1),
|
|
TRUE);
|
|
|
|
-- old_data and new_data both populated for UPDATE
|
|
SELECT morbac.t('UPDATE audit entry: old modality = permission',
|
|
(SELECT old_data->>'modality' FROM morbac.audit_log
|
|
WHERE table_name = 'rules' AND operation = 'UPDATE'
|
|
LIMIT 1) = 'permission',
|
|
TRUE);
|
|
|
|
SELECT morbac.t('UPDATE audit entry: new modality = prohibition',
|
|
(SELECT new_data->>'modality' FROM morbac.audit_log
|
|
WHERE table_name = 'rules' AND operation = 'UPDATE'
|
|
LIMIT 1) = 'prohibition',
|
|
TRUE);
|
|
|
|
-- Clean up the audited rule
|
|
DELETE FROM morbac.rules WHERE id = 'a0000000-0000-0000-0000-000000000001';
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 5: All operations queryable by record_id
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 5. Query audit log by record_id ---'
|
|
|
|
-- The test rule had INSERT, UPDATE, DELETE - should be 3 entries
|
|
SELECT morbac.t_eq('Audit log has 3 entries for test rule record (INSERT + UPDATE + DELETE)',
|
|
(SELECT COUNT(*) FROM morbac.audit_log
|
|
WHERE table_name = 'rules'
|
|
AND record_id = 'a0000000-0000-0000-0000-000000000001')::bigint,
|
|
3);
|
|
|
|
-- All three operations present
|
|
SELECT morbac.t('INSERT, UPDATE, DELETE all present for test rule record',
|
|
(SELECT COUNT(DISTINCT operation) FROM morbac.audit_log
|
|
WHERE table_name = 'rules'
|
|
AND record_id = 'a0000000-0000-0000-0000-000000000001')::bigint = 3,
|
|
TRUE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 6: Multiple tables audited independently
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 6. Multiple tables audited independently ---'
|
|
|
|
SELECT morbac.enable_audit('delegations');
|
|
|
|
-- Insert a delegation to trigger audit on the delegations table
|
|
INSERT INTO morbac.delegations
|
|
(id, delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until)
|
|
VALUES (
|
|
'de000099-0000-0000-0000-000000000099',
|
|
'30000000-0000-0000-0000-000000000001', -- Alice (CEO)
|
|
'30000000-0000-0000-0000-000000000011', -- Karl
|
|
'20000000-0001-0000-0000-000000000001', -- ceo role
|
|
'10000000-0000-0000-0000-000000000001',
|
|
now(),
|
|
now() + interval '1 hour'
|
|
);
|
|
|
|
-- delegations table now has audit entries
|
|
SELECT morbac.t('Audit log has at least 1 entry for delegations table',
|
|
(SELECT COUNT(*) FROM morbac.audit_log WHERE table_name = 'delegations') >= 1,
|
|
TRUE);
|
|
|
|
-- All three tables have audit entries
|
|
SELECT morbac.t('user_roles, rules, and delegations all have audit entries',
|
|
(SELECT COUNT(DISTINCT table_name) FROM morbac.audit_log
|
|
WHERE table_name IN ('user_roles', 'rules', 'delegations'))::bigint = 3,
|
|
TRUE);
|
|
|
|
-- Clean up the test delegation
|
|
DELETE FROM morbac.delegations WHERE id = 'de000099-0000-0000-0000-000000000099';
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 7: Disable audit removes trigger
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 7. Disable audit removes trigger ---'
|
|
|
|
SELECT morbac.disable_audit('user_roles');
|
|
|
|
-- Audit trigger must be removed from user_roles (other system triggers may remain)
|
|
SELECT morbac.t('No audit trigger on user_roles after disable_audit',
|
|
(SELECT COUNT(*) = 0 FROM information_schema.triggers
|
|
WHERE event_object_schema = 'morbac'
|
|
AND event_object_table = 'user_roles'
|
|
AND trigger_name LIKE '%audit%'),
|
|
TRUE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 8: After disabling, changes are no longer logged
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 8. Disable audit stops logging ---'
|
|
|
|
-- Record current count for user_roles before post-disable operations
|
|
-- (2 entries from sections 2+3)
|
|
DO $$
|
|
DECLARE
|
|
count_before BIGINT;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO count_before FROM morbac.audit_log WHERE table_name = 'user_roles';
|
|
-- Store in a temp table for later assertion
|
|
CREATE TEMP TABLE IF NOT EXISTS audit_count_check (cnt BIGINT);
|
|
DELETE FROM audit_count_check;
|
|
INSERT INTO audit_count_check VALUES (count_before);
|
|
END;
|
|
$$;
|
|
|
|
-- Perform another user_roles change - should NOT be logged
|
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id)
|
|
VALUES (
|
|
'30000000-0000-0000-0000-000000000011',
|
|
'20000000-0001-0000-0000-000000000005', -- intern
|
|
'10000000-0000-0000-0000-000000000001'
|
|
);
|
|
|
|
DELETE FROM morbac.user_roles
|
|
WHERE user_id = '30000000-0000-0000-0000-000000000011'
|
|
AND role_id = '20000000-0001-0000-0000-000000000005';
|
|
|
|
-- Count after operations must equal count before (no new entries)
|
|
SELECT morbac.t('user_roles audit count unchanged after post-disable operations',
|
|
(SELECT COUNT(*) FROM morbac.audit_log WHERE table_name = 'user_roles')::bigint
|
|
= (SELECT cnt FROM audit_count_check),
|
|
TRUE);
|
|
|
|
DROP TABLE IF EXISTS audit_count_check;
|
|
|
|
-- Disable the remaining audit triggers
|
|
SELECT morbac.disable_audit('rules');
|
|
SELECT morbac.disable_audit('delegations');
|
|
|
|
-- All audit triggers removed
|
|
SELECT morbac.t('No audit triggers remain after all disable_audit calls',
|
|
(SELECT COUNT(*) = 0 FROM information_schema.triggers
|
|
WHERE event_object_schema = 'morbac'
|
|
AND trigger_name LIKE '%audit%'),
|
|
TRUE);
|
|
|
|
\echo ''
|
|
\echo '=== Audit Logging Tests Completed ==='
|