Files
pgmorbac/tests/06_cross_org.sql
T

303 lines
13 KiB
SQL

-- =============================================================================
-- Cross-Organization Rules Tests
-- =============================================================================
-- Tests inter-organizational access via the cross_org_rules table.
--
-- Scenarios:
-- 1. No cross-org rule: users in org A cannot access org B resources
-- 2. Cross-org permission: specific role in source org can access target org
-- 3. User must hold role in source org (not just any role)
-- 4. Cross-org prohibition: blocks access even if there's a regular permission
-- 5. Cross-org rules combine with activity hierarchy
-- 6. Cross-org rules combine with view hierarchy
-- 7. Temporal cross-org rules (valid_from / valid_until)
--
-- Company context:
-- Judy (sales_rep at Sales) is allowed to read GlobalTech reports via cross-org rule
-- Nina (eng_auditor at Engineering) has a temporal cross-org rule to read GlobalTech audit_logs
-- Diana (sales_manager at Sales) has a cross-org prohibition on writing Engineering docs
--
-- Prerequisites: 00_setup.sql -> 05_temporal.sql
-- =============================================================================
\echo ''
\echo '================================================================'
\echo '06 — CROSS-ORGANIZATIONAL RULES'
\echo '================================================================'
-- ---------------------------------------------------------------------------
-- Setup: additional roles and users for cross-org scenarios
-- ---------------------------------------------------------------------------
-- Nina: eng_auditor role in Engineering Dept
INSERT INTO morbac.roles (id, org_id, name, description) VALUES
('20000000-0002-0000-0000-000000000003', '10000000-0000-0000-0000-000000000002', 'eng_auditor', 'Engineering Auditor');
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
('30000000-0000-0000-0000-000000000014', '20000000-0002-0000-0000-000000000003', '10000000-0000-0000-0000-000000000002');
-- Rules for eng_auditor in Engineering
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality)
SELECT o.id, r.id, 'read', 'audit_logs', c.id, 'permission'
FROM morbac.orgs o
JOIN morbac.roles r ON r.org_id = o.id AND r.name = 'eng_auditor'
JOIN morbac.contexts c ON c.name = 'always'
WHERE o.name = 'Engineering Dept';
-- Verify nina's role was set up correctly
SELECT morbac.t('Nina has eng_auditor role at Engineering',
morbac.user_has_role(
'30000000-0000-0000-0000-000000000014'::uuid,
'10000000-0000-0000-0000-000000000002'::uuid,
'eng_auditor'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 1: No cross-org rule — access between orgs is denied by default
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 1. No cross-org rule: access denied by default ---'
-- Dave (employee at GlobalTech) has no role in Engineering and no cross-org rule
SELECT morbac.t('Dave (GlobalTech employee) reads Engineering documents [no cross-org rule]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000004'::uuid,
'10000000-0000-0000-0000-000000000002'::uuid,
'read', 'documents'
), FALSE);
-- Judy (Engineering engineer) accesses GlobalTech financial_data (no cross-org rule yet)
SELECT morbac.t('Judy (Engineering engineer) reads GlobalTech financial_data [no cross-org rule]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000010'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'financial_data'
), FALSE);
-- ---------------------------------------------------------------------------
-- Section 2: Cross-org permission — Sales sales_rep reads GlobalTech reports
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 2. Cross-org permission ---'
-- Judy is sales_rep at Sales. Add a cross-org rule:
-- Sales sales_rep can read GlobalTech reports
INSERT INTO morbac.cross_org_rules
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
VALUES (
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
'10000000-0000-0000-0000-000000000001', -- target: GlobalTech HQ
'20000000-0003-0000-0000-000000000002', -- sales_rep role
'read', 'reports',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'permission'
);
-- Cross-org rule grants access
SELECT morbac.t('Judy (Sales sales_rep) reads GlobalTech reports via cross-org rule',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000010'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'reports'
), TRUE);
-- Judy at Sales can still read Sales docs normally (local rule, no cross-org needed)
SELECT morbac.t('Judy (Sales sales_rep) reads Sales documents [local rule]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000010'::uuid,
'10000000-0000-0000-0000-000000000003'::uuid,
'read', 'documents'
), TRUE);
-- Judy does NOT have permission to read GlobalTech documents (cross-org rule only covers reports)
SELECT morbac.t('Judy (Sales sales_rep) reads GlobalTech documents [no rule for documents]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000010'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'documents'
), FALSE);
-- ---------------------------------------------------------------------------
-- Section 3: User must hold role in source org
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 3. Role must be held in source org ---'
-- Karl has no role anywhere — cannot use the Sales->GlobalTech cross-org rule
SELECT morbac.t('Karl (no role) reads GlobalTech reports via cross-org rule [no role in source]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000011'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'reports'
), FALSE);
-- Karl also has no access to GlobalTech documents
SELECT morbac.t('Karl (no role) reads GlobalTech documents [no access anywhere]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000011'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'documents'
), FALSE);
-- ---------------------------------------------------------------------------
-- Section 4: Cross-org prohibition — blocks access even with regular permission
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 4. Cross-org prohibition ---'
-- Engineering engineer can normally read Engineering documents (local perm)
SELECT morbac.t('Judy (Engineering engineer) reads Engineering documents [local perm]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000010'::uuid,
'10000000-0000-0000-0000-000000000002'::uuid,
'read', 'documents'
), TRUE);
-- Add Diana (sales_manager at Sales) for cross-org prohibition test
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
('30000000-0000-0000-0000-000000000015', '20000000-0003-0000-0000-000000000001', '10000000-0000-0000-0000-000000000003');
-- Add cross-org prohibition: Sales sales_manager prohibited from writing Engineering documents
INSERT INTO morbac.cross_org_rules
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
VALUES (
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
'10000000-0000-0000-0000-000000000002', -- target: Engineering Dept
'20000000-0003-0000-0000-000000000001', -- sales_manager role
'write', 'documents',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'prohibition'
);
-- Also give a cross-org permission that would otherwise allow the write
INSERT INTO morbac.cross_org_rules
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
VALUES (
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
'10000000-0000-0000-0000-000000000002', -- target: Engineering Dept
'20000000-0003-0000-0000-000000000001', -- sales_manager role
'write', 'documents',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'permission'
);
-- Cross-org prohibition takes precedence over permission
SELECT morbac.t('Diana (Sales sales_manager) writes Engineering documents [cross-org prohibition wins]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000015'::uuid,
'10000000-0000-0000-0000-000000000002'::uuid,
'write', 'documents'
), FALSE);
-- Diana can read Engineering documents (no prohibition for read)
INSERT INTO morbac.cross_org_rules
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
VALUES (
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
'10000000-0000-0000-0000-000000000002', -- target: Engineering Dept
'20000000-0003-0000-0000-000000000001', -- sales_manager role
'read', 'documents',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'permission'
);
-- No prohibition for read, permission applies
SELECT morbac.t('Diana (Sales sales_manager) reads Engineering documents [cross-org permission]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000015'::uuid,
'10000000-0000-0000-0000-000000000002'::uuid,
'read', 'documents'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 5: Cross-org + activity hierarchy
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 5. Cross-org with activity hierarchy ---'
-- The sales_rep cross-org rule grants 'read reports' in GlobalTech.
-- With activity hierarchy (write->read), requesting 'write reports' also matches
-- the 'read' rule (get_effective_activities('write') = {write, read}).
SELECT morbac.t('Judy (Sales sales_rep) writes GlobalTech reports via cross-org + activity hierarchy',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000010'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'write', 'reports'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 6: Cross-org + view hierarchy
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 6. Cross-org with view hierarchy ---'
-- Add a cross-org rule for 'read documents' (not financial_data directly)
-- With view hierarchy (financial_data->documents), 'read documents' cross-org rule
-- also covers 'read financial_data' requests
INSERT INTO morbac.cross_org_rules
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
VALUES (
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
'10000000-0000-0000-0000-000000000001', -- target: GlobalTech HQ
'20000000-0003-0000-0000-000000000002', -- sales_rep role
'read', 'documents',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'permission'
);
-- get_effective_views('financial_data') = {financial_data, documents}
-- so 'read documents' cross-org rule covers 'read financial_data' request
SELECT morbac.t('Judy (Sales sales_rep) reads GlobalTech financial_data via cross-org + view hierarchy',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000010'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'financial_data'
), TRUE);
-- ---------------------------------------------------------------------------
-- Section 7: Temporal cross-org rule
-- ---------------------------------------------------------------------------
\echo ''
\echo '--- 7. Temporal cross-org rule ---'
-- Add a time-limited cross-org rule for eng_auditor to read GlobalTech audit_logs
INSERT INTO morbac.cross_org_rules
(source_org_id, target_org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
VALUES (
'10000000-0000-0000-0000-000000000002', -- source: Engineering
'10000000-0000-0000-0000-000000000001', -- target: GlobalTech HQ
'20000000-0002-0000-0000-000000000003', -- eng_auditor role
'read', 'audit_logs',
(SELECT id FROM morbac.contexts WHERE name = 'always'),
'permission',
now() - interval '1 hour',
now() + interval '1 day'
);
-- Nina (Engineering eng_auditor) reads GlobalTech audit_logs within window
SELECT morbac.t('Nina (Engineering eng_auditor) reads GlobalTech audit_logs [temporal cross-org, active]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000014'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'audit_logs'
), TRUE);
-- Expire the cross-org rule
UPDATE morbac.cross_org_rules
SET valid_until = now() - interval '1 second'
WHERE source_org_id = '10000000-0000-0000-0000-000000000002'
AND target_org_id = '10000000-0000-0000-0000-000000000001'
AND role_id = '20000000-0002-0000-0000-000000000003'
AND activity = 'read' AND view = 'audit_logs';
-- Temporal rule expired
SELECT morbac.t('Nina (Engineering eng_auditor) reads GlobalTech audit_logs [temporal cross-org, expired]',
morbac.is_allowed_nocache(
'30000000-0000-0000-0000-000000000014'::uuid,
'10000000-0000-0000-0000-000000000001'::uuid,
'read', 'audit_logs'
), FALSE);
\echo ''
\echo '=== Cross-Organization Rules Tests Completed ==='