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 ==='
|