Files
pgmorbac/tests/15_rls_check.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

225 lines
8.5 KiB
SQL

-- =============================================================================
-- rls_check Tests
-- =============================================================================
-- Tests morbac.rls_check() with all session-org combinations. A NULL row org
-- is an unattributed object: an org filter (single pin, or org_ids without a
-- null marker) excludes it; it is reachable via no filter or a null marker.
--
-- 1. No user_id set: always FALSE
-- 2. Single org context
-- a. org-scoped row, matching org
-- b. org-scoped row, different org (blocked)
-- c. NULL row: filtered out under an org pin (orphan not requested)
-- 3. org_ids filter
-- a. org-scoped row in list
-- b. org-scoped row not in list (blocked)
-- c. NULL row, list without null marker: filtered out even with a grant
-- c2. NULL row, list with null marker + global permission: allowed
-- d. NULL row, list with null marker + global prohibition: blocked
-- e. NULL row, list with null marker + no rule: blocked
-- 4. No org context
-- a. org-scoped row: uses row's org
-- b. NULL row + global permission [orphan + global rules -> TRUE]
-- c. NULL row + global prohibition [blocked]
-- d. NULL row + no rule [blocked]
--
-- User state carried from previous tests:
-- Karl (30000000-0000-0000-0000-000000000011):
-- - user_rules: read documents, read reports in GlobalTech HQ (no expiry)
-- - no role assignments, no org memberships
--
-- Prerequisites: 00_setup.sql -> 14_system_principals.sql
-- =============================================================================
\echo ''
\echo '================================================================'
\echo '15 -- RLS_CHECK'
\echo '================================================================'
-- ---------------------------------------------------------------------------
-- Section 1: No user_id set - always FALSE
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 1. No user_id: always FALSE ---'
RESET morbac.user_id;
RESET morbac.org_id;
RESET morbac.org_ids;
SELECT morbac.t('rls_check without user_id, org row',
morbac.rls_check('read', 'documents',
'10000000-0000-0000-0000-000000000001'::uuid),
FALSE);
SELECT morbac.t('rls_check without user_id, global row',
morbac.rls_check('read', 'contracts', NULL),
FALSE);
-- ---------------------------------------------------------------------------
-- Section 2: Single org context
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 2. Single org context ---'
SET morbac.user_id = '30000000-0000-0000-0000-000000000011'; -- Karl
SET morbac.org_id = '10000000-0000-0000-0000-000000000001'; -- GlobalTech HQ
-- 2a: org-scoped row, matching org - Karl has user_rule for read documents
SELECT morbac.t('rls_check single org, org row matches session org (Karl/documents)',
morbac.rls_check('read', 'documents',
'10000000-0000-0000-0000-000000000001'::uuid),
TRUE);
-- 2b: org-scoped row, different org - blocked before is_allowed
SELECT morbac.t('rls_check single org, org row from different org (blocked)',
morbac.rls_check('read', 'documents',
'10000000-0000-0000-0000-000000000002'::uuid),
FALSE);
-- 2c: NULL row - filtered out under a single org pin (orphan not requested)
SELECT morbac.t('rls_check single org, NULL row filtered out under org pin',
morbac.rls_check('read', 'documents', NULL),
FALSE);
-- 2c (contracts): still filtered out regardless of permission
SELECT morbac.t('rls_check single org, NULL row filtered out (contracts)',
morbac.rls_check('read', 'contracts', NULL),
FALSE);
RESET morbac.user_id;
RESET morbac.org_id;
-- ---------------------------------------------------------------------------
-- Section 3: org_ids filter
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 3. org_ids filter ---'
SET morbac.user_id = '30000000-0000-0000-0000-000000000011'; -- Karl
SET morbac.org_ids = '["10000000-0000-0000-0000-000000000001"]'; -- [GlobalTech HQ]
-- 3a: org-scoped row in the list - Karl has user_rule for read documents in GlobalTech
SELECT morbac.t('rls_check org_ids, org row in list (Karl/documents/GlobalTech)',
morbac.rls_check('read', 'documents',
'10000000-0000-0000-0000-000000000001'::uuid),
TRUE);
-- 3b: org-scoped row not in the list - blocked
SELECT morbac.t('rls_check org_ids, org row not in list (blocked)',
morbac.rls_check('read', 'documents',
'10000000-0000-0000-0000-000000000002'::uuid),
FALSE);
-- 3c: NULL row, list WITHOUT null marker - filtered out even with a global grant
INSERT INTO morbac.global_rules (user_id, activity, view, context_id, modality)
VALUES (
'30000000-0000-0000-0000-000000000011', -- Karl
'read', 'contracts',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'permission'
);
SELECT morbac.t('rls_check org_ids without null marker, NULL row filtered out despite grant',
morbac.rls_check('read', 'contracts', NULL),
FALSE);
-- 3c2: NULL row, list WITH null marker + global permission - allowed
SET morbac.org_ids = '["10000000-0000-0000-0000-000000000001", null]';
SELECT morbac.t('rls_check org_ids with null marker, NULL row + global permission',
morbac.rls_check('read', 'contracts', NULL),
TRUE);
DELETE FROM morbac.global_rules
WHERE user_id = '30000000-0000-0000-0000-000000000011'
AND activity = 'read' AND view = 'contracts';
-- 3d: NULL row, list with null marker + global prohibition
INSERT INTO morbac.global_rules (user_id, activity, view, context_id, modality)
VALUES (
'30000000-0000-0000-0000-000000000011', -- Karl
'read', 'contracts',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'prohibition'
);
SELECT morbac.t('rls_check org_ids with null marker, NULL row + global prohibition',
morbac.rls_check('read', 'contracts', NULL),
FALSE);
DELETE FROM morbac.global_rules
WHERE user_id = '30000000-0000-0000-0000-000000000011'
AND activity = 'read' AND view = 'contracts';
-- 3e: NULL row, list with null marker + no rule
SELECT morbac.t('rls_check org_ids with null marker, NULL row + no rule',
morbac.rls_check('read', 'contracts', NULL),
FALSE);
RESET morbac.user_id;
RESET morbac.org_ids;
-- ---------------------------------------------------------------------------
-- Section 4: No org context
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 4. No org context ---'
SET morbac.user_id = '30000000-0000-0000-0000-000000000011'; -- Karl
-- 4a: org-scoped row - uses row's org_id (Karl has user_rule in GlobalTech)
SELECT morbac.t('rls_check no org context, org row: uses row org (Karl/documents/GlobalTech)',
morbac.rls_check('read', 'documents',
'10000000-0000-0000-0000-000000000001'::uuid),
TRUE);
-- 4a (no permission): Karl has no rule in Engineering
SELECT morbac.t('rls_check no org context, org row: no permission in row org (Engineering)',
morbac.rls_check('read', 'documents',
'10000000-0000-0000-0000-000000000002'::uuid),
FALSE);
-- 4b: NULL row + global permission - is_allowed(Karl, NULL, ...) sees unattributed + global rules
INSERT INTO morbac.global_rules (user_id, activity, view, context_id, modality)
VALUES (
'30000000-0000-0000-0000-000000000011', -- Karl
'read', 'contracts',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'permission'
);
SELECT morbac.t('rls_check no org context, global row + global permission [new: was FALSE]',
morbac.rls_check('read', 'contracts', NULL),
TRUE);
DELETE FROM morbac.global_rules
WHERE user_id = '30000000-0000-0000-0000-000000000011'
AND activity = 'read' AND view = 'contracts';
-- 4c: global row + global prohibition
INSERT INTO morbac.global_rules (user_id, activity, view, context_id, modality)
VALUES (
'30000000-0000-0000-0000-000000000011', -- Karl
'read', 'contracts',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'prohibition'
);
SELECT morbac.t('rls_check no org context, global row + global prohibition',
morbac.rls_check('read', 'contracts', NULL),
FALSE);
DELETE FROM morbac.global_rules
WHERE user_id = '30000000-0000-0000-0000-000000000011'
AND activity = 'read' AND view = 'contracts';
-- 4d: global row + no rule
SELECT morbac.t('rls_check no org context, global row + no rule',
morbac.rls_check('read', 'contracts', NULL),
FALSE);
RESET morbac.user_id;
\echo ''
\echo '=== rls_check Tests Completed ==='