feat(tests): add more tests and make them pass
This commit is contained in:
+314
-67
@@ -1,82 +1,329 @@
|
||||
-- ============================================================================
|
||||
-- MORBAC PostgreSQL Extension - Temporal Constraint Tests
|
||||
-- ============================================================================
|
||||
-- This test file covers temporal constraints including:
|
||||
-- - Rules with valid_from timestamps (future activation)
|
||||
-- - Rules with valid_until timestamps (expiration)
|
||||
-- - Rules with both valid_from and valid_until (time windows)
|
||||
-- - Authorization decisions respecting temporal bounds
|
||||
-- =============================================================================
|
||||
-- Temporal Constraint Tests
|
||||
-- =============================================================================
|
||||
-- Tests time-bounded rules using valid_from and valid_until on the rules table.
|
||||
--
|
||||
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||
-- ============================================================================
|
||||
-- Cases:
|
||||
-- 1. is_rule_valid() helper function tested directly
|
||||
-- 2. is_active flag set by trigger on insert/update
|
||||
-- 3. Expired rule (valid_until in the past) -> denied
|
||||
-- 4. Future rule (valid_from in the future) -> denied
|
||||
-- 5. Active time window (valid_from past, valid_until future) -> allowed
|
||||
-- 6. Multiple rules for same combination — only active ones count
|
||||
-- 7. Expired prohibition: no longer blocks access after it expires
|
||||
-- 8. Temporal rules interact correctly with role hierarchy
|
||||
--
|
||||
-- Note: Direct rule inserts are used (bypassing Policy DSL) to control timestamps.
|
||||
-- Uses a dedicated view 'temp_view' for isolation so existing rules aren't disturbed.
|
||||
--
|
||||
-- Prerequisites: 00_setup.sql -> 04_constraints.sql
|
||||
-- =============================================================================
|
||||
|
||||
\echo ''
|
||||
\echo '=== Temporal Constraints on Rules ==='
|
||||
\echo '================================================================'
|
||||
\echo '05 — TEMPORAL CONSTRAINTS'
|
||||
\echo '================================================================'
|
||||
|
||||
-- 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
|
||||
);
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Setup: dedicated view for temporal tests
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO morbac.views (name, description) VALUES
|
||||
('temp_view', 'Scratch view for temporal tests');
|
||||
|
||||
-- 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
|
||||
);
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Section 1: is_rule_valid() helper function
|
||||
-- ---------------------------------------------------------------------------
|
||||
\echo ''
|
||||
\echo '--- 1. is_rule_valid() helper ---'
|
||||
|
||||
-- Create an active time-bounded rule
|
||||
-- No bounds: always valid
|
||||
SELECT morbac.t('is_rule_valid(NULL, NULL) — always valid',
|
||||
morbac.is_rule_valid(NULL::timestamptz, NULL::timestamptz), TRUE);
|
||||
|
||||
-- Past valid_from, no valid_until: currently active
|
||||
SELECT morbac.t('is_rule_valid(past, NULL) — started in past, no end',
|
||||
morbac.is_rule_valid('2000-01-01'::timestamptz, NULL), TRUE);
|
||||
|
||||
-- Future valid_from: not yet active
|
||||
SELECT morbac.t('is_rule_valid(future, NULL) — not yet started',
|
||||
morbac.is_rule_valid('2099-01-01'::timestamptz, NULL), FALSE);
|
||||
|
||||
-- Past valid_until: expired
|
||||
SELECT morbac.t('is_rule_valid(NULL, past) — already expired',
|
||||
morbac.is_rule_valid(NULL, '2000-01-01'::timestamptz), FALSE);
|
||||
|
||||
-- Future valid_until, no valid_from: currently active
|
||||
SELECT morbac.t('is_rule_valid(NULL, future) — no start, future end',
|
||||
morbac.is_rule_valid(NULL, '2099-01-01'::timestamptz), TRUE);
|
||||
|
||||
-- Active window: past start, future end
|
||||
SELECT morbac.t('is_rule_valid(past, future) — within active window',
|
||||
morbac.is_rule_valid('2000-01-01'::timestamptz, '2099-01-01'::timestamptz), TRUE);
|
||||
|
||||
-- Fully past window (both start and end in the past)
|
||||
SELECT morbac.t('is_rule_valid(past_start, past_end) — entirely expired',
|
||||
morbac.is_rule_valid('2000-01-01'::timestamptz, '2001-01-01'::timestamptz), FALSE);
|
||||
|
||||
-- Fully future window (both start and end in the future)
|
||||
SELECT morbac.t('is_rule_valid(future_start, future_end) — entirely in the future',
|
||||
morbac.is_rule_valid('2090-01-01'::timestamptz, '2099-01-01'::timestamptz), FALSE);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Section 2: is_active flag set by trigger on insert
|
||||
-- ---------------------------------------------------------------------------
|
||||
\echo ''
|
||||
\echo '--- 2. is_active trigger on insert ---'
|
||||
|
||||
-- Insert a currently active 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',
|
||||
'10000000-0000-0000-0000-000000000001',
|
||||
'20000000-0001-0000-0000-000000000004', -- employee
|
||||
'audit', 'temp_view',
|
||||
(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
|
||||
now() - interval '1 day',
|
||||
now() + interval '1 day'
|
||||
);
|
||||
|
||||
\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;
|
||||
-- is_active should be TRUE for the active rule
|
||||
SELECT morbac.t('is_active = TRUE for rule with active time window',
|
||||
(SELECT is_active FROM morbac.rules WHERE activity = 'audit' AND view = 'temp_view'),
|
||||
TRUE);
|
||||
|
||||
-- Update to expired window, trigger should recalculate is_active
|
||||
UPDATE morbac.rules
|
||||
SET valid_until = now() - interval '1 second'
|
||||
WHERE activity = 'audit' AND view = 'temp_view';
|
||||
|
||||
-- is_active should be FALSE after expiry update
|
||||
SELECT morbac.t('is_active = FALSE after valid_until set to past',
|
||||
(SELECT is_active FROM morbac.rules WHERE activity = 'audit' AND view = 'temp_view'),
|
||||
FALSE);
|
||||
|
||||
-- Clean up
|
||||
DELETE FROM morbac.rules WHERE activity = 'audit' AND view = 'temp_view';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Section 3: Expired rule — valid_until in the past
|
||||
-- ---------------------------------------------------------------------------
|
||||
\echo ''
|
||||
\echo '--- 3. Expired rule (valid_until in the past) ---'
|
||||
|
||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_until)
|
||||
VALUES (
|
||||
'10000000-0000-0000-0000-000000000001',
|
||||
'20000000-0001-0000-0000-000000000004', -- employee
|
||||
'export', 'temp_view',
|
||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||
'permission',
|
||||
'2020-01-01 00:00:00+00' -- expired years ago
|
||||
);
|
||||
|
||||
-- Rule expired, no valid permission
|
||||
SELECT morbac.t('Dave (employee) exports temp_view via expired rule [denied]',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000004'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'export', 'temp_view'
|
||||
), FALSE);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Section 4: Future rule — valid_from in the future
|
||||
-- ---------------------------------------------------------------------------
|
||||
\echo ''
|
||||
\echo '--- 4. Future rule (valid_from in the future) ---'
|
||||
|
||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from)
|
||||
VALUES (
|
||||
'10000000-0000-0000-0000-000000000001',
|
||||
'20000000-0001-0000-0000-000000000004', -- employee
|
||||
'manage', 'temp_view',
|
||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||
'permission',
|
||||
'2099-01-01 00:00:00+00' -- won't start for decades
|
||||
);
|
||||
|
||||
-- Rule not yet active
|
||||
SELECT morbac.t('Dave (employee) manages temp_view via future rule [not yet active]',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000004'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'manage', 'temp_view'
|
||||
), FALSE);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Section 5: Active time window (valid_from past, valid_until future)
|
||||
-- ---------------------------------------------------------------------------
|
||||
\echo ''
|
||||
\echo '--- 5. Active time window ---'
|
||||
|
||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
|
||||
VALUES (
|
||||
'10000000-0000-0000-0000-000000000001',
|
||||
'20000000-0001-0000-0000-000000000004', -- employee
|
||||
'read', 'temp_view',
|
||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||
'permission',
|
||||
'2020-01-01 00:00:00+00',
|
||||
'2099-12-31 23:59:59+00'
|
||||
);
|
||||
|
||||
-- Rule is within its validity window
|
||||
SELECT morbac.t('Dave (employee) reads temp_view via active window rule',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000004'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'read', 'temp_view'
|
||||
), TRUE);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Section 6: Multiple rules for same combination, only active ones count
|
||||
-- ---------------------------------------------------------------------------
|
||||
\echo ''
|
||||
\echo '--- 6. Multiple rules for same combination, only active ones count ---'
|
||||
|
||||
-- At this point, employee has expired export rule (section 3) and active read rule (section 5).
|
||||
-- Since export->read in activity hierarchy, the active read rule grants export access.
|
||||
SELECT morbac.t('Dave exports temp_view (expired export + active read via export->read hierarchy)',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000004'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'export', 'temp_view'
|
||||
), TRUE);
|
||||
|
||||
-- Activate the expired export rule (unique constraint prevents a second rule with same modality)
|
||||
UPDATE morbac.rules
|
||||
SET valid_from = now() - interval '1 hour',
|
||||
valid_until = now() + interval '1 hour'
|
||||
WHERE org_id = '10000000-0000-0000-0000-000000000001'
|
||||
AND role_id = '20000000-0001-0000-0000-000000000004'
|
||||
AND activity = 'export' AND view = 'temp_view' AND modality = 'permission';
|
||||
|
||||
-- Now export rule is active directly (not just via read hierarchy)
|
||||
SELECT morbac.t('Dave exports temp_view (export rule now active)',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000004'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'export', 'temp_view'
|
||||
), TRUE);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Section 7: Temporal prohibition — expired prohibition no longer blocks
|
||||
-- ---------------------------------------------------------------------------
|
||||
\echo ''
|
||||
\echo '--- 7. Expired prohibition no longer blocks ---'
|
||||
|
||||
-- Add an active permission for intern -> read temp_view
|
||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality)
|
||||
VALUES (
|
||||
'10000000-0000-0000-0000-000000000001',
|
||||
'20000000-0001-0000-0000-000000000005', -- intern
|
||||
'read', 'temp_view',
|
||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||
'permission'
|
||||
);
|
||||
|
||||
-- Add an EXPIRED prohibition for intern -> read temp_view
|
||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_until)
|
||||
VALUES (
|
||||
'10000000-0000-0000-0000-000000000001',
|
||||
'20000000-0001-0000-0000-000000000005', -- intern
|
||||
'read', 'temp_view',
|
||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||
'prohibition',
|
||||
'2020-01-01 00:00:00+00' -- prohibition expired years ago
|
||||
);
|
||||
|
||||
-- Prohibition expired, permission stands
|
||||
SELECT morbac.t('Eve (intern) reads temp_view [active permission, expired prohibition]',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000005'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'read', 'temp_view'
|
||||
), TRUE);
|
||||
|
||||
-- Now replace the prohibition with an active one
|
||||
UPDATE morbac.rules
|
||||
SET valid_until = now() + interval '1 day'
|
||||
WHERE org_id = '10000000-0000-0000-0000-000000000001'
|
||||
AND role_id = '20000000-0001-0000-0000-000000000005'
|
||||
AND activity = 'read' AND view = 'temp_view' AND modality = 'prohibition';
|
||||
|
||||
-- Active prohibition now blocks
|
||||
SELECT morbac.t('Eve (intern) reads temp_view [active prohibition blocks]',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000005'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'read', 'temp_view'
|
||||
), FALSE);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Section 8: Temporal rule with role hierarchy
|
||||
-- ---------------------------------------------------------------------------
|
||||
\echo ''
|
||||
\echo '--- 8. Temporal rule with role hierarchy ---'
|
||||
|
||||
-- Expire the intern read temp_view prohibition (activated in section 7) so it does not
|
||||
-- block Carol (manager) and Alice (CEO) who inherit from intern via role hierarchy.
|
||||
UPDATE morbac.rules
|
||||
SET valid_until = now() - interval '1 second'
|
||||
WHERE org_id = '10000000-0000-0000-0000-000000000001'
|
||||
AND role_id = '20000000-0001-0000-0000-000000000005'
|
||||
AND activity = 'read' AND view = 'temp_view' AND modality = 'prohibition';
|
||||
|
||||
-- Add an active 'approve temp_view' permission for manager.
|
||||
-- 'approve' is NOT in the activity hierarchy (manage->delete->write->read, export->read),
|
||||
-- so employee's 'read temp_view' rule does NOT cover 'approve temp_view'.
|
||||
-- This ensures the temporal test cleanly verifies expiry without hierarchy interference.
|
||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
|
||||
VALUES (
|
||||
'10000000-0000-0000-0000-000000000001',
|
||||
'20000000-0001-0000-0000-000000000003', -- manager
|
||||
'approve', 'temp_view',
|
||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||
'permission',
|
||||
now() - interval '1 hour',
|
||||
now() + interval '1 hour'
|
||||
);
|
||||
|
||||
-- Carol (manager) can approve temp_view directly
|
||||
SELECT morbac.t('Carol (manager) approves temp_view [temporal rule, active]',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000003'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'approve', 'temp_view'
|
||||
), TRUE);
|
||||
|
||||
-- Alice (CEO) inherits from manager — should also get the temporal permission
|
||||
SELECT morbac.t('Alice (CEO, inherits manager) approves temp_view [temporal rule, active]',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000001'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'approve', 'temp_view'
|
||||
), TRUE);
|
||||
|
||||
-- Expire the manager approve rule
|
||||
UPDATE morbac.rules
|
||||
SET valid_until = now() - interval '1 second'
|
||||
WHERE org_id = '10000000-0000-0000-0000-000000000001'
|
||||
AND role_id = '20000000-0001-0000-0000-000000000003'
|
||||
AND activity = 'approve' AND view = 'temp_view';
|
||||
|
||||
-- Rule expired for both manager and CEO
|
||||
SELECT morbac.t('Carol (manager) approves temp_view after rule expiry [denied]',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000003'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'approve', 'temp_view'
|
||||
), FALSE);
|
||||
|
||||
SELECT morbac.t('Alice (CEO, inherits manager) approves temp_view after rule expiry [denied]',
|
||||
morbac.is_allowed_nocache(
|
||||
'30000000-0000-0000-0000-000000000001'::uuid,
|
||||
'10000000-0000-0000-0000-000000000001'::uuid,
|
||||
'approve', 'temp_view'
|
||||
), FALSE);
|
||||
|
||||
\echo ''
|
||||
\echo '=== Temporal Constraint Tests Completed ==='
|
||||
|
||||
Reference in New Issue
Block a user