Files
pgmorbac/tests/08_system_access.sql
T

291 lines
11 KiB
SQL

-- =============================================================================
-- System Access Tests
-- =============================================================================
-- morbac system tables are protected by RLS.
-- is_allowed() and its internals are SECURITY DEFINER to avoid recursion.
-- System view names are config-driven (system_view.* keys, default: orgs/roles/rules/...).
--
-- Scenarios:
-- 1. Default deny: no rule = no access to system views via is_allowed()
-- 2. Grant permissions via regular rules, verify access
-- 3. Prohibition overrides permission (standard engine behavior)
-- 4. Role hierarchy applies: senior role inherits permissions
-- 5. assign_role() / revoke_role() — SoD/cardinality enforcement, RLS guards the INSERT/DELETE
-- 6. RLS on morbac tables: session user cannot read/write without rules
-- 7. Rules are org-scoped
--
-- Prerequisites: 00_setup.sql -> 07_audit.sql
-- =============================================================================
\echo ''
\echo '================================================================'
\echo '08 — SYSTEM ACCESS'
\echo '================================================================'
-- ---------------------------------------------------------------------------
-- Section 1: Default deny
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 1. Default deny: no rule = no access to system views ---'
SELECT morbac.t('Carol (manager) create rules [default deny]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000003'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'rules'
), FALSE);
SELECT morbac.t('Grace (hr_manager) create user_roles [default deny]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000007'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'user_roles'
), FALSE);
-- ---------------------------------------------------------------------------
-- Section 2: Grant permissions via regular rules
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 2. Grant permissions ---'
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality)
SELECT o.id, r.id, v.activity, v.view, c.id, v.modality::morbac.modality
FROM (VALUES
('GlobalTech HQ', 'manager', 'create', 'rules', 'always', 'permission'),
('GlobalTech HQ', 'manager', 'delete', 'rules', 'always', 'permission'),
('GlobalTech HQ', 'hr_manager', 'read', 'user_roles', 'always', 'permission'),
('GlobalTech HQ', 'hr_manager', 'create', 'user_roles', 'always', 'permission'),
('GlobalTech HQ', 'hr_manager', 'delete', 'user_roles', 'always', 'permission'),
('GlobalTech HQ', 'director', 'create', 'roles', 'always', 'permission'),
('GlobalTech HQ', 'director', 'read', 'roles', 'always', 'permission'),
('GlobalTech HQ', 'director', 'update', 'roles', 'always', 'permission'),
('GlobalTech HQ', 'director', 'delete', 'roles', 'always', 'permission')
) AS v(org_name, role_name, activity, view, context_name, modality)
JOIN morbac.orgs o ON o.name = v.org_name
JOIN morbac.roles r ON r.org_id = o.id AND r.name = v.role_name
JOIN morbac.contexts c ON c.name = v.context_name;
\echo ''
\echo '--- 3. Verify permissions ---'
SELECT morbac.t('Carol (manager) create rules',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000003'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'rules'
), TRUE);
SELECT morbac.t('Carol (manager) delete rules',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000003'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'delete', 'rules'
), TRUE);
SELECT morbac.t('Carol (manager) update rules [no rule]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000003'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'update', 'rules'
), FALSE);
SELECT morbac.t('Grace (hr_manager) create user_roles',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000007'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'user_roles'
), TRUE);
SELECT morbac.t('Dave (employee) create rules [no rule]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000004'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'rules'
), FALSE);
-- ---------------------------------------------------------------------------
-- Section 4: Role hierarchy applies
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 4. Role hierarchy applies ---'
SELECT morbac.t('Bob (director, inherits manager) create rules',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000002'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'rules'
), TRUE);
SELECT morbac.t('Bob (director) create roles [own rule]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000002'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'roles'
), TRUE);
SELECT morbac.t('Alice (CEO) create roles [inherits director]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000001'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'roles'
), TRUE);
SELECT morbac.t('Eve (intern) create rules [nothing in hierarchy]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000005'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'rules'
), FALSE);
-- ---------------------------------------------------------------------------
-- Section 5: Prohibition overrides permission
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 5. Prohibition overrides permission ---'
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality)
SELECT o.id, r.id, 'create', 'rules', c.id, 'prohibition'
FROM morbac.orgs o
JOIN morbac.roles r ON r.org_id = o.id AND r.name = 'manager'
JOIN morbac.contexts c ON c.name = 'always'
WHERE o.name = 'GlobalTech HQ';
SELECT morbac.t('Carol (manager, prohibited) create rules [prohibition wins]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000003'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'create', 'rules'
), FALSE);
SELECT morbac.t('Carol (prohibition only on create) delete rules [still allowed]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000003'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'delete', 'rules'
), TRUE);
DELETE FROM morbac.rules
WHERE org_id = '10000000-0000-0000-0000-000000000001'
AND role_id = '20000000-0001-0000-0000-000000000003'
AND activity = 'create' AND view = 'rules'
AND modality = 'prohibition';
-- ---------------------------------------------------------------------------
-- Section 6: assign_role / revoke_role
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 6. assign_role / revoke_role ---'
-- As DB owner (bypasses RLS), assign Karl as intern to verify constraint logic
SELECT morbac.assign_role(
'30000000-0000-0000-0000-000000000011'::uuid, -- Karl
'20000000-0001-0000-0000-000000000005'::uuid, -- intern
'10000000-0000-0000-0000-000000000001'::uuid
);
SELECT morbac.t('Karl assigned intern role via assign_role()',
EXISTS(
SELECT 1 FROM morbac.user_roles
WHERE user_id = '30000000-0000-0000-0000-000000000011'
AND role_id = '20000000-0001-0000-0000-000000000005'
), TRUE);
SELECT morbac.revoke_role(
'30000000-0000-0000-0000-000000000011'::uuid,
'20000000-0001-0000-0000-000000000005'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid
);
SELECT morbac.t('Karl intern role revoked via revoke_role()',
NOT EXISTS(
SELECT 1 FROM morbac.user_roles
WHERE user_id = '30000000-0000-0000-0000-000000000011'
AND role_id = '20000000-0001-0000-0000-000000000005'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 7: RLS on morbac tables
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 7. RLS on morbac tables ---'
-- Create a non-superuser role so RLS policies are enforced (superusers bypass RLS by default)
DROP ROLE IF EXISTS morbac_rls_tester;
CREATE ROLE morbac_rls_tester;
GRANT USAGE ON SCHEMA morbac TO morbac_rls_tester;
GRANT SELECT, INSERT, DELETE ON morbac.user_roles TO morbac_rls_tester;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA morbac TO morbac_rls_tester;
SET SESSION AUTHORIZATION morbac_rls_tester;
-- Grace (hr_manager) has create/delete on user_roles — RLS should allow
SET morbac.user_id = '30000000-0000-0000-0000-000000000007';
SET morbac.org_id = '10000000-0000-0000-0000-000000000001';
INSERT INTO morbac.user_roles (user_id, role_id, org_id)
VALUES (
'30000000-0000-0000-0000-000000000011',
'20000000-0001-0000-0000-000000000005',
'10000000-0000-0000-0000-000000000001'
);
SELECT morbac.t('Grace (hr_manager) inserted user_role via RLS',
EXISTS(
SELECT 1 FROM morbac.user_roles
WHERE user_id = '30000000-0000-0000-0000-000000000011'
AND role_id = '20000000-0001-0000-0000-000000000005'
), TRUE);
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';
-- Dave (employee) has no rules for user_roles — RLS should block
SET morbac.user_id = '30000000-0000-0000-0000-000000000004';
SET morbac.org_id = '10000000-0000-0000-0000-000000000001';
DO $$
BEGIN
INSERT INTO morbac.user_roles (user_id, role_id, org_id)
VALUES (
'30000000-0000-0000-0000-000000000011',
'20000000-0001-0000-0000-000000000005',
'10000000-0000-0000-0000-000000000001'
);
RAISE NOTICE 'CHECK FAIL: Dave (employee) inserted user_role [should have been blocked by RLS]';
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'CHECK PASS: Dave (employee) blocked from inserting user_role by RLS';
END;
$$;
RESET SESSION AUTHORIZATION;
RESET morbac.user_id;
RESET morbac.org_id;
DROP OWNED BY morbac_rls_tester;
DROP ROLE IF EXISTS morbac_rls_tester;
-- ---------------------------------------------------------------------------
-- Section 8: Rules are org-scoped
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 8. Rules are org-scoped ---'
SELECT morbac.t('Carol (GlobalTech manager) create Engineering rules [wrong org]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000003'::uuid,
'10000000-0000-0000-0000-000000000002'::uuid,
'create', 'rules'
), FALSE);
SELECT morbac.t('Alice (GlobalTech CEO) create Engineering roles [org-scoped]',
morbac.is_allowed(
'30000000-0000-0000-0000-000000000001'::uuid,
'10000000-0000-0000-0000-000000000002'::uuid,
'create', 'roles'
), FALSE);
\echo ''
\echo '=== System Access Tests Completed ==='